
`vary mutate --fast-mode` narrows the per-mutant test filter on top of the existing coverage-guided selection. It trades a controlled amount of accuracy for speed, and runs a continuous validation pass every run so that accuracy stays within a threshold you set. When the measured miss rate exceeds the threshold, every non-sampled mutant is re-run with the broader filter and the report is rewritten with the conservative result.

For the basics, see [Introduction](/docs/mutation/testing/). For how fast mode composes with other strict-mode optimisations, see [Strict mode](/docs/mutation/strict-mode/).

## Why "validated"

Fast mode is heuristic. Without continuous validation it would be a silently-lossy classification engine, turning a survivor the conservative filter would have killed into "test gap" noise in the report even though the test exists and works. Continuous validation is the contract that lets Vary ship a faster mode and still defend the per-mutant `killed` / `survived` classification.

The validation compares `(fast-mode classification, broad classification)` on a sample of mutants. A "miss" is a sampled mutant where the two classifications disagree in either direction.

| Disagreement | Meaning |
|--------------|---------|
| `fast=survived, broad=killed` | Fast mode missed a kill |
| `fast=killed, broad=survived` | Fast mode reported a phantom kill |

If miss rate > threshold, fast mode auto-falls back: every remaining survivor is re-run with the conservative filter and the result list is rewritten. Every mutant in the final report is then labeled by the conservative truth.

## Narrowing rule

For each mutant, the conservative test filter is the set of tests that exercise the mutated method (from the stats-phase coverage map). Fast mode takes the first `max(1, ceil(N * narrowFactor))` tests in deterministic sort order.

| `narrowFactor` | Effect |
|----------------|--------|
| `0.5` (default) | Keeps half the conservative filter |
| `0.25` | Keeps a quarter |
| `1.0` | No-op |

When a mutant has no coverage filter (stats phase did not run, or the method has no covering tests), fast mode does not narrow. Conservative behaviour applies.

## CLI flags

| Flag | Default | Range | Purpose |
|------|---------|-------|---------|
| `--fast-mode` | off | flag | Enable narrowing + validation |
| `--fast-mode-narrow-factor` | `0.5` | `[0.0, 1.0]` | Fraction of conservative filter kept |
| `--fast-mode-sample-size` | `10` | `>= 0` | Mutants re-run with the broader filter |
| `--fast-mode-miss-threshold` | `0.10` | `[0.0, 1.0]` | Miss rate above which auto-fallback fires |

The threshold rule is **strictly greater than**: a miss rate that equals the threshold does not trigger fallback. This makes the boundary deterministic so a workload sitting exactly on the line stays in fast mode.

## Artifact shape

`mutation.json` per-file block:

```json
{
  "file": "src/util.vary",
  "fastMode": {
    "enabled": true,
    "narrowFactor": 0.5000,
    "sampleSize": 10,
    "sampledMutants": 10,
    "missCount": 3,
    "missRate": 0.3000,
    "missRateThreshold": 0.1000,
    "fallbackTriggered": true
  },
  "mutants": [...]
}
```

The same six fields are surfaced as columns in `telemetry.json` and `telemetry.csv` (`fastModeEnabled`, `fastModeNarrowFactor`, `fastModeSampledMutants`, `fastModeMissRate`, `fastModeMissRateThreshold`, `fastModeFallbackTriggered`). These columns are emitted unconditionally so downstream consumers can read the same shape every run.

## Interaction with other features

| Feature | Interaction |
|---------|-------------|
| [Incremental inference](/docs/mutation/incremental-inference/) (`--incremental-infer`) | Fast mode narrows the test filter that is stored as each entry's `testsFingerprint`. Switching between fast mode and conservative invalidates prior entries, which is correct because the stored result was produced under a different test filter. |
| Mutant grouping | The grouping plan is computed from the conservative covering-tests map. Fast mode's narrowing applies on top of the group's `selectedTests`, so members of a group still share the same narrowed filter. |
| [Survivor tail](/docs/mutation/survivor-tail/) | The post-loop flake-detection rerun runs before the fast-mode validation pass and uses the same (narrowed) filter the main loop used. The validation pass is a separate, accounted-for cost and adds to `executionTimeMs` but is not part of the survivor tail. |
| Bytecode backends | Fast mode is wired into the AST-level runner only. The bytecode backends (`fresh-loader`, `hot-swap`, `redefine`) do not apply fast-mode narrowing. |

## Non-goals

| Non-goal | Detail |
|----------|--------|
| Automatic per-workload narrow-factor selection | Start with a hand-tuned value, watch the miss rate, adjust |
| Persisting fast-mode validation history across runs | Each run re-validates |
| Replacing strict mode as the truth source | Fast mode is an opt-in; strict mode (the no-narrowing path) remains the default |
