Files
craft-ai-integrations/tools/mutation/README.md
T
imperator e8f320321a feat(ai-engine-burn): InSPyReNet for Remove background, built in only (#112)
* feat(ai-engine-burn): InSPyReNet for image.matte (Remove background)

A second model on the Burn engine: `inspyrenet` answers `image.matte` from the
picture alone, a grayscale PNG at the picture's size (white is the subject, soft
along hair and fur). `remove-background` has had no local model; this is one.

- Network: InSPyReNet with the Swin-B backbone, written by hand in Burn after the
  MIT reference code. Module names follow the PyTorch keys, so the published
  .safetensors loads by name through Burn's PyTorch adapter. A load that leaves a
  weight unset, fails one, or finds a foreign tensor is refused in plain words.
- Any picture size: full resolution up to 1280 px on the long side; above that a
  reflect-padded square reduced with antialiased Lanczos-3, the matte enlarged
  back and refined at full resolution with a colour guided filter on the tiles
  near the edge (the Gyre approach), then a 0.2 fringe shrink.
- Weights: 1038lab/inspyrenet inspyrenet.safetensors at a pinned revision,
  checked tensor for tensor against the author's ckpt_base.pth.

Checked on one Windows machine (release): every stage against
transparent-background's PyTorch code (worst relative difference 3.2e-5 on the
processor, 2.4e-4 on the card, no pixel changing side); the shared conformance
suite; on a real PNG the mask is byte-identical to the same port in PhotoCraft.
Four new deliberate-bug cases in `be`, all caught. Not done: a catalogue recipe
(a recipe names a ComfyUI workflow today), clean timings, macOS and Linux runs,
a run through an app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-features): a recipe that is built in only, InSPyReNet for Remove background

`LocalRecipe::comfyui` (default true) marks a recipe ComfyUI runs. With false it is only its
built-in routes: never a ComfyUI setup option, plan, install offer or connection, and engine
advice does not list ComfyUI for a tool without a ComfyUI recipe. `Feature::comfyui_recipes()`
gives those recipes; `engine_recipe`, `plan_native`, `setup_offer_native` and `enable_native`
are unchanged.

`remove-background` gets one such recipe: InSPyReNet on Burn, the pinned
`inspyrenet.safetensors` (1038lab/inspyrenet @ 0f594663, 367,120,444 bytes) in the folder
`inspyrenet`, so Set up AI offers one checked download and no ComfyUI.

Tests: the catalogue, setup options, plans and offers (`ai-features/tests/built_in_only.rs`),
registration and advice (`ai-client/tests/built_in_only.rs`), and through `AiClient` on Burn
(`ai-client/tests/burn_matte.rs`, the real run ignored). Mutation group `bo`: 8 of 8 caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tools): retarget two `lc` mutation cases at the ComfyUI recipe lookups

`unrunnable-recipe-accepted` and `second-recipe-takes-plain-id` searched for the
`feature.locals` lookups in `ai-client/src/local.rs`, which now go through
`Feature::comfyui_recipes()`. Same mutations, new targets; group `lc`: 21 caught, one
known equivalent. Every case in `GROUPS` again has exactly one target (640 checked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ai-engine-burn): InSPyReNet runner checks around a stand-in, readout without panic

CI has no graphics card, only a software renderer (lavapipe, WARP), and the engine takes it.
There the tiny network was too slow for the conformance suite's poll limit, and on lavapipe the
readout failed. Unoptimised, Burn's processor backend takes minutes for the same network.

- The loaded model runs the pipeline around a `Predict`: the network, or in tests a stand-in,
  as the SAM 2.1 tests here use a fake backend. The conformance suite and the mask test use the
  stand-in (debug: the crate's unit tests in about 18 s).
- The tiny network still runs through load and run on the processor in an ignored release test
  (about 4 s). The runner's device choice is a field, so tests can name the processor.
- The prediction is read with `try_into_data`, so a device that fails while running is an
  error, not a guarded panic.
- docs/BURN-ENGINE.md: the test split and that a software renderer counts as a card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-engine-burn): InSPyReNet moves to the processor when the card fails

A card can answer the engine's probe and still fail on the network: on CI a software
renderer (lavapipe) failed while reading the answer back. The first failure on a card that is
not a cancel now builds the network again on the processor from the same file, runs that
picture again there with a notice, and stays on the processor (`inspyrenet/fallback.rs`).
A network loaded on the processor in the first place has nothing to move to.

Tests with stand-ins: a working card is kept; a failing one moves once and stays; a cancel
is not a failure; a rebuild that fails is the answer. Mutation group `be`: three new cases,
all seven InSPyReNet cases caught. A real photo on the card still runs on the card.

docs/BURN-ENGINE.md: the move, and the client run's picture is 2848 x 4272, not 3101 x 1613.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ai-engine-burn): the move to the processor stays only after a rebuild that worked

The rebuild is tried once. If it fails, its error is the answer and later pictures go to the
card again, as `a_rebuild_that_fails_is_the_answer` already pins. The module docs and
docs/BURN-ENGINE.md said the model stays on the processor without that condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-engine-burn): retry a failed InSPyReNet load on the card on the processor

The move to the processor covered failures while predicting, not a load on the card that
fails. `load_or_processor` now retries such a load on the processor, unless the file itself is
not the checkpoint (`Unsupported`), which is refused at once rather than read a second time.
A network loaded on the processor in the first place is loaded once.

Tests: a card that loads keeps the network; one that fails loads on the processor; a bad file
is not read twice; on the processor there is one load. Mutation group `be`: two new cases, all
nine InSPyReNet cases caught; every case in the checker has one target (645).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(ai-engine-burn): method references in the guided filter, one name for the fallback path

Three SonarCloud findings on #112: two closures in `guided.rs` become method references, and the
mutation cases name `inspyrenet/fallback.rs` once. No change in behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: imperator <imperator.maximus@gmail.com>
2026-10-30 19:34:26 +00:00

4.3 KiB

Deliberate-bug checks

Each case breaks one safety rule in the source, runs the one test that should notice, and puts the source back. A case is caught only if the mutated code compiled and the test run failed. A mutant that does not compile counts as not caught, so every survivor is either a test gap (fix the test) or a bad mutant (fix the mutant). Cargo runs serially, targeted with -p and --locked, honouring CARGO_BUILD_JOBS from the build resource.

Script Groups
mutation_check.py u1 u3 u4 p4 p10 v1 v2 v3 sd vx lc lr nr ct u2 wf (connections, model sources, node packs, media, client flows, declarative adapter, workflow library). Cases for u2 are in mutation_u2.py.
mutation_check.py seg Shared selection contract: points, box, description and checked result metadata.
mutation_check.py rt rten model runner, bounded picture decoding, resampling and prompt assembly.
mutation_check.py rt rten engine picture handling: bounded PNG and JPEG decoding, mask encoding, resampling.
mutation_check.py bp Local Burn weight preparation (ai-burn-setup): source hashes, cache key, damaged saved weights, cancel.
mutation_check.py be Burn engine: saved-weights loading, the panic guard, answer checks, file names, its catalogue route, the .bpk binding rule and the shared architecture revision.
mutation_check.py bo A recipe only built-in runtimes run is never offered, planned, installed, registered or advised as a ComfyUI model.
mutation_check.py fr First-run setup flow: the licence tick only where needed, the one start button, the suggested folder, adopting checked files, the route-aware try-it sample, status and picker when a file has gone, route notes and engine advice. Cases in mutation_first_run.py.
mutation_check.py gs Generic selection: scratch sinks, the refusal of clicks and mapped selection workflows.
mutation_check.py gs Generic selection workflows: the scratch sinks, the refusal of clicks and mapped selection workflows.
mutation_check.py gu Generic selection uploads: one upload per picture, the bounded memory, the resend after a cleared scratch area or a failed job.
mutation_check.py gm Generic selection masks: size, one channel, the empty-mask answer and the picture header.
mutation_check.py s21 SAM 2.1 selection (sam21_session.rs, detector.rs): the Florence-2-large preflight, the detector name and the choice check.
mutation_check.py ca Upstream contract alignment: model-test address provenance, required inputs, and app-facing refusal before key access. Cases in mutation_contract.py.
mutation_check.py ps Node pack status report (pack/status.rs): links, hidden folders, names, the install record.
mutation_check.py vx Vertex express adapter (ai-vertex/src/lib.rs): the API key travels in the x-goog-api-key header, never in the request URL.
mutation_check.py lg Remembered licences (registry/licences.rs, install.rs): terms, versions, local files, damaged record.
mutation_check.py eg Connection panel lifecycle (ai-egui/src/panel.rs): the same-frame Save dedup and the stale-answer generation guard. Cases in mutation_egui.py; receipt and logs in verification/eg-mutations.json.
sam3_mutation_check.py sg1 sg2 sg2b sg2c sg3 (SAM 3 masks, sessions, slots, cancel).
python tools/mutation/mutation_check.py u2            # one group
python tools/mutation/mutation_check.py u2 stale      # cases whose name contains "stale"

The "Deliberate-bug checks" workflow runs a group on Ubuntu and macOS. It takes the group and an optional case-name filter, so one case can be checked on both systems in minutes instead of waiting for the group. A filter that names no case in the group fails instead of reporting success:

group:  p4
filter: input-folder-read

Group ids are historical labels. The header of each test file says what it covers.

Run them exclusively. They snapshot and restore source files, so do not edit, format or build those files while one runs. Afterwards read the restored source and rerun the focused tests.

Logs go to tools/mutation/verification/ (not committed) after passing through scrub.py, which replaces a Windows or Unix profile folder with %USERPROFILE% so no account name is written.