
`vary mutate --incremental-infer` reuses prior mutant outcomes when neither the mutant's code nor its relevant tests changed, turning a repeat `vary mutate` run into a sequence of lookups instead of fresh bytecode patching and test dispatch.

Unlike `--incremental`, which short-circuits an entire file when the source and test files match the prior run, `--incremental-infer` makes the reuse decision per mutant. New mutants still execute; prior ones are re-used only when every safety gate passes.

For the basics, see [Introduction](/docs/mutation/testing/).

## Enabling

```bash
vary mutate src/foo.vary -t tests/test_foo.vary --incremental-infer
```

The inference artifact is written to `.vary/inference/<12-char-source-path-hash>.inference` relative to the working directory. The hash is derived from the absolute source path so two files with the same basename in different directories never collide.

## Artifact format

Text-based, human-inspectable, schema version 1:

```text
# Vary Mutation Inference
schema_version=1
source_hash=<sha-256 of source file>
test_hash=<sha-256 of test file>
compiler_version=<compiler version at write time>
operators=<sorted, comma-separated operator set>
timestamp=<ms since epoch>

# Entries
<mutant-id>|<outcome>|<killed-by;...>|<execution-ms>|<code-fp>|<tests-fp>
```

Outcomes persisted: `KILLED`, `SURVIVED`, `TIMEOUT`, `EQUIVALENT`. Non-equivalent engine `ERROR`s are never persisted, so a transient compilation or runtime failure cannot poison a future run.

## Safety gates

Any gate that fails forces fresh execution for the affected mutant(s). The gates are conservative by design.

### File-level gates (applied when the store is opened)

All must hold; otherwise the entire entry set is dropped.

| Gate | What must match |
|------|-----------------|
| Schema version | Current code's `SCHEMA_VERSION` |
| Source hash | SHA-256 of the current source file |
| Test hash | SHA-256 of the current test file |
| Compiler version | Current `ArtifactId.COMPILER_VERSION` |
| Operator set | Stored `operators` is a non-empty subset of the current run's operator set |

The operator-set rule means widening the set (for example, adding `relational`) keeps prior `arithmetic` entries inferrable; narrowing invalidates the entire store.

### Per-mutant gates

Even after the store loads, each mutant is re-checked.

| Gate | What it validates |
|------|-------------------|
| Stable id | The mutation's stable id matches a stored entry. An operator change that removes or renames the mutation simply fails to look up. |
| `codeFingerprint` | Equals `sha-256(symbolPath|type|original|mutated|line)` for the current run's mutation. Catches a generator evolving to emit a different transform for the same id. |
| `testsFingerprint` | Equals `sha-256(sorted-csv of covering-test names, or "*" if no coverage)`. Catches changes to the covering-tests set even when source and tests are byte-identical. |
| Stored outcome | Is not `ERROR`. |

## Output labelling

Each mutant in the JSON report carries two redundant fields:

```json
{
  "id": "add#ARITHMETIC#0",
  "killed": true,
  "inferred": false,
  "resolution": "executed"
}
```

| Combination | Meaning |
|-------------|---------|
| `inferred: true` / `resolution: "inferred"` | Outcome reused from a prior compatible run |
| `inferred: false` / `resolution: "executed"` | Freshly executed (no prior entry applied, or a gate failed) |

## Survivor flake rerun

The [survivor-tail](/docs/mutation/survivor-tail/) flake-detection rerun skips inferred mutants, because re-running them would defeat the point of inference. A mutant killed in the prior run via a flake-induced rerun is persisted as `KILLED` with its original `killedBy`, so inference preserves the final classification, not the intermediate one.

## Non-goals

| Non-goal | Detail |
|----------|--------|
| Per-method or per-statement invalidation granularity | Any edit to the source file invalidates all entries |
| Cross-file dependency tracking | If the mutated file depends on another module and that module changes, the gate on this file's `source_hash` still passes. Delete `.vary/inference/` to force a fresh run. |
| Project-mode inference | Inference is wired into the single-file AST runner path used by `vary mutate <file.vary>`. Project-mode (`vary mutate <dir>`) does not pass an inference file through to the per-file runner. |
| Bytecode-backend inference | The `fresh-loader`, `hot-swap`, and `redefine` backends each own their own execution loop and do not yet consult the AST-level inference store. |
