Turn a service failure into a permanent CI guard¶
This companion to the service evidence loop takes one recovery failure to a portable CI guard.
explore → coverage → exact replay → bounded finding → portable regression
→ verify --ci → workload-strength control
Before you start: name the promise¶
Tell Ordeal how to reach the service, which container it may disrupt, and what a correct response looks like:
[compose]
file = "compose.test.yaml"
base_url = "http://127.0.0.1:8080"
services = ["worker"]
faults = ["kill"]
replay_attempts = 3
[[compose.requests]]
name = "read_order"
path = "/orders/{order_id}"
expect_status = 200
expect_json = {"json.state" = "committed"}
expect_status and expect_json are business promises. Start with safe GET requests; mutating requests are not faultable by default.
1. Explore the real service¶
Ordeal keeps the topology alive, introduces faults, waits for recovery, and reports operation × fault × property:
| Status | Meaning |
|---|---|
PASS |
This exact combination ran and the promise held |
FAIL |
This exact combination ran and the promise broke |
NOT EXERCISED |
The combination did not run; it is still a gap |
2. Demand repeated, exact replay¶
attempted 3 / reproduced 3 means three exact action signatures matched; external timing is not assumed deterministic.
For an existing trace, choose the count explicitly:
3. Save only a bounded finding¶
Promotion requires a replay match on failure kind, message, action index, and action name. Ordeal then writes:
The portable trace and shared manifest use repository-relative paths and are CI-provider neutral.
Harness and configuration errors remain diagnostics, not service defects.
4. Prove red, then prove green¶
Before the fix, exit 1 proves the bound witness still catches the bug. After the fix, exit 0 requires every
configured attempt to complete cleanly. A different failure is not accepted as a fix.
Commit the portable trace and manifest together, then guard all records:
verify --ci is read-only and provider-neutral. It rejects hashes or paths that escape the workspace.
It needs Docker Compose and any credentials referenced by the trace.
5. Test that the workload can notice wrong answers¶
After the fixed control is clean, add a bounded budget:
Ordeal first replays an unchanged response. If stable, it changes recorded expectations and checks the workload.
killed means it caught the wrong answer; survived means it accepted it; inconclusive means the control was unstable.
This changes recorded expectations, not application code. It measures only observed trace prefixes.
Run the checked-in acceptance example¶
With Docker Compose available, run from the repository root:
uv run python scripts/verify_compose_evidence_loop.py \
--output .artifacts/compose-evidence-loop.json
uv run python scripts/verify_compose_service_matrix.py \
--recovery-report .artifacts/compose-evidence-loop.json \
--output .artifacts/compose-service-matrix.json
The first fixture proves a kill/restart defect and normal-CLI post-fix control. The matrix adds two-service persistence across API restart and eight-way fan-out under delay and corruption faults. Every declared cell passes and each workload catches four wrong expectations.
Evidence boundary and deeper reference¶
This proves a bounded observation and post-fix control, not root cause, deterministic scheduling, or universal correctness.
Related: configuration, traces and replay, CI and operations, and the durable regression schema.