e54e3f42

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

sydtest: control-group (no-op) mutations

Add control-group mutations: deliberate no-ops that are expected to survive.
The plugin wraps an expression as `(\ctrl -> ifMutation cmid ctrl ctrl) e` (the
same expression on both branches, so a genuine non-diff), one after every 12
real mutations. Both branches share the subtree through the lambda binder, so
the no-op does not duplicate the instrumented expression. The control rides the
existing operator field under the reserved name "Control"; no new record field.

A control changes no behaviour, so killing it is impossible unless the mutation
testing is unsound. Tests are already retried, so a killed control is a real
defect (a flaky/nondeterministic suite or a harness bug), not transient noise,
and it fails the run like a survivor: assert-score fails on it, the driver's
fail-fast exit trips on it, and isMutationFailure is True for it. A passing
control never trips anything and is excluded from the killed/survived score.

MutationRunReport is split into three parts to match: a MutationTally (the
normal killed/survived/timed-out/uncovered/skipped score), a ControlTally
(passed/failed), and the per-mutation groups; the JSON nests to match. Record
types used as formal arguments use RecordWildCards.