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.