CaseCrop documentation

Reduce a failing execution.

CaseCrop starts with an execution that already fails. It deletes events, replays what remains, and keeps a smaller case only when the same failure is reproduced.

Install and run

Python 3.10 or newer. The package has no runtime dependencies. Install the beta from its tagged GitHub release; PyPI distribution is not available yet.

pip install "casecrop @ git+https://github.com/shi1720/casecrop.git@v0.1.1"
casecrop demo --case cache --require-minimal

The smallest API example below uses a teaching oracle. The bundled lab and cart integration replay actual state transitions against buggy and corrected implementations.

from casecrop import Event, Outcome, Trace, minimize

trace = Trace([
    Event("setup", {"op": "create"}),
    Event("noise", {"op": "log"}),
    Event("trigger", {"op": "read"}, requires=("setup",)),
])

def replay(events):
    # Replace this teaching oracle with fresh application replay.
    ids = {event.id for event in events}
    return Outcome.fail("my-bug.v1") if "trigger" in ids else Outcome.pass_()

result = minimize(trace, replay, max_calls=200)
assert result.reduced.ids == ("setup", "trigger")
assert result.one_minimal
result.reduced.save("regression.json")
result.save("evidence.json")

Connect your application

Your application owns replay. Start from a clean in-memory object, test database, container, or disposable fixture on every call. Map application operations to events and declare the setup each operation needs.

git clone https://github.com/shi1720/casecrop.git
cd casecrop
python -m pip install -e '.[test]'
python examples/cart_workflow.py
python -m pytest examples/test_cart_regression.py

casecrop reduce examples/cart_trace.json \
  --output cart-incident --require-minimal \
  --oracle python examples/cart_oracle.py '{trace}'

The cart example converts an application log into events. Two coupons produce a negative total. CaseCrop removes unrelated views, preserves the item prerequisite, and leaves a three-event regression. The corrected cart caps its total at zero.

Read the complete integration

Trace format

A trace is an ordered list of uniquely named events. Payloads are finite JSON values. Access returns a detached copy so accidental mutation inside an oracle cannot change the next experiment.

{
  "schema_version": 1,
  "events": [
    {"id": "create", "payload": {"op": "create"}},
    {"id": "read", "payload": {"op": "read"},
     "requires": ["create"], "pinned": false, "cost": 1}
  ]
}
FieldMeaning
idUnique nonempty event ID, up to 200 characters.
payloadApplication-owned JSON. It is data, never dynamically imported code.
requiresIDs of earlier prerequisites. Missing or forward dependencies are rejected.
pinnedProtect the event and, transitively, its prerequisites.
costPositive integer metadata. The reducer optimizes deletions, not weighted cost.

When a prerequisite is deleted, its dependents are deleted too. Every candidate keeps the original order. Dependencies are declared by you; CaseCrop does not infer them from arbitrary I/O.

Replay outcomes

Outcome.fail("cache.cross_tenant")  # The target behavior occurred.
Outcome.pass_()                     # Valid execution; target is absent.
Outcome.unresolved("fixture down")  # Cannot reach a trustworthy verdict.

The original trace must consistently fail. CaseCrop learns its signature, or you can require one with signature=. An unrelated failure signature is unresolved. Unexpected Python exceptions propagate; an accidental crash never becomes success.

repeats=3 requires all three observations to agree. Mixed verdicts or failure signatures are unresolved. Stable results are cached within the run; unresolved results are retried. The last retained case gets a fresh replay that bypasses the cache. These checks can detect some flakiness, but cannot prove determinism.

Result status and guarantees

With a deterministic oracle and a complete audit, no permitted single-event deletion, together with its dependent events, retains the target failure.

This is closure 1-minimality. It is relative to your pins, prerequisites, and oracle. It is not a globally shortest trace, proof of causality, or verification of the oracle itself. Nonmonotonic bugs may have smaller cases that this local search cannot reach.

StatusWhat happened
completeThe final replay and every permitted deletion check passed.
budget_exhaustedA smaller known failing candidate may exist; verification is incomplete.
unresolvedFinal reproduction succeeded, but some deletion trials could not be classified.
unstableThe fresh final replay did not reproduce the target.

max_calls counts physical oracle invocations, including baseline, repeats, audit, and confirmation. Cache hits use no calls. one_minimal stays false if the budget runs out. Check it before using an artifact as a fully audited result.

Command-line replay protocol

The command adapter runs an explicit argument vector. Put a {trace} placeholder wherever your program expects the candidate file. Your process writes a single outcome object to stdout and exits zero.

{"verdict":"fail","signature":"cart.negative_total"}
{"verdict":"pass"}
{"verdict":"unresolved","reason":"fixture unavailable"}

Place all CaseCrop options before --oracle; everything after it belongs to your program. A timeout, nonzero exit, invalid JSON, or excess output is unresolved. The CLI never interprets shell operators.

casecrop reduce incident.json \
  --output reduced-incident \
  --signature cart.negative_total \
  --timeout 5 --max-calls 200 --require-minimal \
  --oracle python my_replay.py '{trace}'

Output contains reduced.json and report.json. Demo output also includes an executable test_regression.py. Existing output artifacts are protected from overwrite. Exit 0 means the operation completed; with --require-minimal, incomplete verification exits 1. Invalid input or setup errors exit 2.

System architecture

Recorded events
IDs + payloads + prerequisites
Reducer
Delete → replay → compare
Regression case
Trace + evidence + audit

The Python package contains the model, reduction engine, command adapter, and CLI. Your replay function owns the system under test. The React lab executes those same source files in a Pyodide Web Worker and displays the resulting report. There is no model judge or second reducer in TypeScript.

Full architecture and decisions

Limits and prior work

Use isolated replay fixtures. The Python callback is trusted code, and the command adapter is not a sandbox. It inherits your environment and permissions. A synchronous callback cannot be preempted by the call budget. POSIX commands have process-group timeout cleanup; Windows cleanup covers the direct process only.

Traces can contain sensitive payloads. There is no automatic redaction: sanitize before capture or sharing. The lab keeps input in the browser and runs only its three bundled replay systems. Use Python or the CLI for your own oracle. Browser inputs are limited to 200 events and 256 KB of raw and normalized UTF-8 JSON. Invalid Unicode and integers outside JavaScript’s safe range are rejected. The library accepts up to 10,000 events and 8 MB of canonical JSON.

CaseCrop builds on delta debugging by Zeller and Hildebrandt and dependency graph reduction. Picire is a mature alternative with parallel reduction. Hypothesis generates and shrinks structured examples. CaseCrop focuses on existing event histories, explicit replay contracts, and auditable regression artifacts.

Open the workbench