steady-state-hypothesis-validator
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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill steady-state-hypothesis-validatorsteady-state-hypothesis-validator
Overview
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 (opens in new window) 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 Chaos Toolkit steady-state-hypothesis block
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/ (opens in new window) and chaostoolkit.org/reference/concepts/ (opens in new window).
The five pre-flight checks
Check 1 - Metric is measurable and observable
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:
Pass signal: The team can run the probe in isolation right now and get a numeric or boolean return value.
Check 2 - A recent baseline exists
Per principlesofchaos.org (opens in new window): "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:
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.
Check 3 - Tolerance is numerically meaningful and SLI-backed
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:
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:
Check 4 - Measurement window is defined
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:
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: 200Check 5 - The metric moves under the target failure mode
The 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:
Worked example
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.
Hard-reject conditions
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 (opens in new window) 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 |
Output format
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>Limitations
References
Chaos Toolkit steady-state-hypothesis block and tolerance forms
View source (opens in new window)Chaos Toolkit steady-state-hypothesis block and tolerance forms
Reference for steady-state-hypothesis-validator. The five pre-flight checks in SKILL.md validate a hypothesis expressed in this schema; this file is the schema and tolerance detail they assume.
The steady-state-hypothesis object
Per chaostoolkit.org/reference/api/experiment/ (opens in new window), the steady-state-hypothesis object requires:
Tolerance forms supported
Per chaostoolkit.org/reference/api/experiment/ (opens in new window):
| Tolerance form | Syntax example | Evaluation |
|---|---|---|
| Scalar equality | "tolerance": 200 | probe return == 200 |
| Boolean equality | "tolerance": true | probe return == true |
| String equality | "tolerance": "OK" | probe return == "OK" |
| Inclusive range | "tolerance": [95, 100] | 95 <= value <= 100 |
| Membership | "tolerance": [200, 201, 204] | value in list |
| Regex | "tolerance": {"type": "regex", "pattern": "^healthy$"} | regex match |
| JSONPath | "tolerance": {"type": "jsonpath", "path": "$.status", "expect": "up"} | JSONPath extract + compare |
| Range object | "tolerance": {"type": "range", "range": [95.0, 100.0]} | numeric bounds |
Execution flow
Per chaostoolkit.org/reference/concepts/ (opens in new window): probes run once before the method (baseline check) and once after (deviation check). A probe that fails before the method means the system is already outside its acceptable state; the experiment must not run. A probe that fails after the method means the chaos activity caused the system to leave its steady state.
Related skills
chaos-drill-protocol
Run protocol for a chaos experiment that has already been designed: the four pre-flight gates (non-production target, measured healthy baseline, live observability, a rollback that has actually been exercised), how to pick a conservative blast-radius bound, the sampling cadence and abort criteria fixed in writing before injection, and the recovery-validation step with its tolerance and timeout. Owns execution safety only, not experiment design: the steady-state hypothesis, the fault to inject, and the experiment file come from elsewhere. Use when an experiment definition exists and a fault is about to be injected into a running system, and the go/no-go gates, abort thresholds, and recovery check still need to be agreed and written down before the fault starts.
chaos-experiment-author
Build-an-X workflow for a chaos experiment per the Principles of Chaos Engineering - defines steady-state hypothesis, picks the variables (real-world events: network latency, node failure, region outage), sets the blast radius (which percentage / namespace / user cohort), automates execution, and emits the verdict (steady-state held / didn't hold). Use to scope a chaos experiment before running it via Litmus / Chaos Mesh / Gremlin / Toxiproxy.
chaos-mesh
Configures Chaos Mesh for Kubernetes-native chaos engineering - picks fault types (PodChaos, NetworkChaos, StressChaos, IOChaos, TimeChaos, DNSChaos, KernelChaos, HTTPChaos), targets via label selectors, controls blast radius via namespace whitelists + selector filters, schedules via CronJobs, observes via dashboard. Distinct from Litmus by architecture (Chaos Mesh has its own dashboard + workflow orchestration; Litmus uses ChaosCenter UI). Use when the target system runs on Kubernetes and fault experiments should be declared as CRDs in the cluster alongside the workloads they target.
chaos-results-reporter
Aggregates chaos drill verdicts over time into a resilience trend report - per-experiment hypothesis-held / blast-radius / time-to-detect / time-to-recover, degradation trends across runs, action items, and a stakeholder summary. Use when a team has completed one or more chaos drills and needs a structured trend report showing whether resilience is improving, degrading, or stable across iterations.
failure-injection-test-author
Orchestrates WireMock fault stubs (HTTP-level fault: 500s, malformed JSON, slow responses) with Toxiproxy (TCP-level: latency, packet loss, reset) into a single resilience test scenario - the test starts both, applies fault per scenario, runs the SUT against the impaired endpoints, verifies the SUT's resilience patterns. Use when one test must reproduce a combined network + HTTP failure - a cross-layer failure mode from an incident postmortem that neither pure HTTP fault stubs nor pure TCP chaos can cover alone, because most real failures span both layers.
gremlin-chaos
Configures Gremlin (commercial) for cross-platform chaos engineering (fault injection, resilience testing) - installs the Gremlin agent on Linux / Windows / Kubernetes, picks attack types (resource, network, state, request), chains attacks into Scenarios (chaos experiments), integrates with the Reliability Score for forward-looking metrics. Use when the platform spans multiple environments (bare metal + cloud + serverless) and the team needs a commercial-supported solution per Gremlin's multi-platform support.
litmus-chaos
Configures LitmusChaos for Kubernetes-native chaos engineering - installs via Helm, picks ChaosExperiments from the ChaosHub (`pod-delete`, `network-latency`, `node-cpu-hog`, etc.), authors a ChaosEngine CR scoping the experiment + steady-state probes, runs as part of the cluster, exports Prometheus metrics for the verdict. Use when the platform is Kubernetes (CNCF-hosted; cloud-native). Prefer over chaos-mesh when the team wants a ChaosCenter web UI for workflow scheduling and ChaosHub catalog browsing; use chaos-mesh for fine-grained network-fault policies via its own CRD family.
toxiproxy-chaos
Configures Toxiproxy for TCP-level fault injection - runs as a sidecar / proxy between client and upstream, applies toxics (latency, bandwidth, slow_close, timeout, slicer, limit_data, reset_peer) via control API. Focused on the proxy itself rather than an API-level chaos runner, including non-test usage (chaos in dev environments, integration tests, pre-prod simulation). Use when the team needs TCP-precise fault injection in development / integration environments without K8s or commercial tooling.