36e11150

Fix: ignore filter does not suppress RemoveAction on ignored do-block statements

## Summary of the bug

The mutation plugin's `ignore` config (e.g. `ignore: [logDebug, logInfo]`) is
supposed to leave certain calls — and their argument subtrees — completely
untouched. But mutations were still generated for ignored calls used as
`do`-block statements. In `~/src/nix-ci` these surfaced as `RemoveAction`
mutations dropping statements like:

- `forPrometheus prometheusEnvCounterBytesPut $ liftIO . Counter.add (SB.length contents)`
- `logDebug $ Text.pack $ unwords [...]`
- `logDebug "Unauthenticated request"`

## The regression test

`sydtest-mutation-example/src/Example/IgnoreLib.hs` gains `markM` (a no-op
`Writer` action, the monadic analogue of the existing `mark` marker) and a
`markAction` `do`-block that uses it as a non-binding statement:

```haskell
markAction = execWriter $ do
  markM "tag"
  tell "body"
```

`markM` is added to the `ignore:` list in `nix/mutation.yaml`. The golden
manifest `Example.IgnoreLib.json` asserts no mutations (`[]`). Before the fix,
the golden manifest check (`checks.x86_64-linux.mutation-manifest-example`)
fails, producing a `RemoveAction` mutation that removes the ignored
`markM "tag"` statement.

## Root cause

The `ignore` filter in `instrumentLExpr` only checks the syntactic head of the
*current* expression. GHC 9.10 expands `do { logDebug "x"; rest }` into
`(>>) (logDebug "x") rest`, whose head is `(>>)` — not `logDebug` — so the
filter does not fire. The `RemoveAction` operator then matches the `(>>)` chain
and removes its LHS action (`logDebug "x"`), which is exactly the noise the
ignore list is meant to silence.

## The fix

`RemoveAction` now consults `instrumentEnvIgnore` and declines to remove an
action whose syntactic head is on the ignore list, in both code paths:

- `mutateChain` — the expanded `(>>) lhs rest` form (the common GHC 9.10 case).
- `rawDoAction` — the raw `HsDo` fallback (list/monad comprehensions).

To keep this silent, `applyOperator` in `Instrument.hs` now treats an operator
action that returns no alternatives as a deliberate non-candidate rather than a
validation failure, so it no longer prints the "all replacements dropped"
warning in this case.

Non-ignored removals are unaffected: `Example.DoLib`'s `RemoveAction` mutations
are unchanged.

## Things to manually check

- In `~/src/nix-ci`, add the relevant names (e.g. `logDebug`, `logInfo`,
  `forPrometheus`) to the `ignore:` list in `mutation.yaml` — the plugin still
  only ignores names that are explicitly configured; there is no built-in
  default ignore list. The three reported sites are all `do`-block statements
  that this fix now suppresses once their head is listed.
- Confirm the mutation report for nix-ci no longer lists the `RemoveAction`
  survivors on those statements after listing their heads under `ignore:`.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 1h05m04s 2s 2s 1h05m06s 1h05m04s
Eval 1h05m46s 42s 42s 1h06m28s 40s
Build 1h08m09s 13s 15s 1h08m25s 1m42s
Suite 1h05m04s 58s 3m21s 1h08m25s 1h07m26s

Timeline

0s1h5m1h6m1h6m1h6m1h8m1h8m