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
Config 1h05m04s 2s 2s 1h05m06s
Eval 1h05m46s 42s 42s 1h06m28s
Build 1h08m09s 13s 15s 1h08m25s
Test - - - -
Deploy - - - -
Suite 1h05m04s 58s 3m21s 1h08m25s

Timeline

0s1h5m1h6m1h6m1h6m1h8m1h8m