mix pd.bisect (PropertyDamage v0.2.0)

View Source

Find the first commit where a saved PropertyDamage failure starts reproducing.

Given a .pd failure file and a known-good commit, this drives git bisect over the history between that commit and a known-bad one (HEAD by default), replaying the saved failure at each candidate commit and classifying it from the replay's exit code. It reports the first commit where the bug appears, then restores the working tree.

This is an orchestrator over git bisect run and mix pd.replay; it adds no engine logic of its own. Each per-commit replay runs in a fresh mix pd.replay subprocess (fresh compile, fresh BEAM), so the result is honest across the recompilation each checkout requires.

Usage

mix pd.bisect path/to/failure.pd --good <ref> [--bad <ref>] [--verbose]
  • --good REF (required): a commit/tag/branch where the bug is known absent.
  • --bad REF (default HEAD): a commit where the bug is known present.
  • --verbose: pass --verbose through to each mix pd.replay.

The failure file records its own model and adapter, so no --model / --adapter flags are needed.

How a commit is classified

Bisect needs each commit sorted into good / bad / skip. mix pd.replay supplies exactly that via its exit code:

  • 0 — every command passed: the bug is fixed at this commit (good).
  • 1 — a command failed its check or errored: the bug reproduces (bad).
  • 125 — the replay could not run at all (the commit does not compile, the model/adapter do not exist yet, the file fails to load, or the sequence is branching): indeterminate, so git bisect skips it rather than blaming it.

The 125 = skip case is load-bearing. Commits older than the bug often predate the model/adapter modules entirely, so the replay cannot run there. Marking such an ancestor "bad" would push the reported regression earlier than the truth and corrupt the result; skipping keeps the search honest. If skipped commits cluster at the good/bad boundary, git bisect reports a set of candidates rather than a single commit, which this task surfaces as-is.

What this bisects (and why it is version-robust)

This replays the saved concrete shrunk sequence, not a re-generation from the seed. That matters because the saved command structs are replayed verbatim, so the bisect stays correct even across commits that changed generators, command weights, or when: predicates (DR-023). Bisecting by seed would silently produce a different sequence after any such drift and is therefore not offered here. The only requirement is that the saved command structs still exist as modules at each tested commit (commits where they do not are skipped, as above).

Traps this task handles for you

  • Tracked failure files vanish on checkout. If the .pd lives inside the repo, it may not exist at the good commit. The file is copied to a temp path outside the working tree before bisecting and replayed from there.
  • A dirty working tree breaks bisect. Uncommitted changes are detected up front; the task errors with a hint and never starts a bisect in that state.
  • Leaving the repo mid-bisect. git bisect reset always runs at the end, on success, error, and exception, so the working tree returns to where it started.

Exit code

  • 0 when a first bad commit (or a candidate set) is identified.
  • non-zero only on an orchestration error: a dirty tree, an invalid --good/--bad ref, a git failure, or a bisect that produced no verdict.

Examples

# Find where a saved failure first appears, from a known-good tag to HEAD
mix pd.bisect failures/currency-bug.pd --good v0.1.0

# Bound both ends explicitly
mix pd.bisect failures/currency-bug.pd --good abc1234 --bad def5678