diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml
index 72a11131e..6dc0e50c9 100644
--- a/.github/workflows/mutants.yml
+++ b/.github/workflows/mutants.yml
@@ -1,3 +1,7 @@
+# Weekly cargo-mutants + PR in-diff advisory. Not a required PR check.
+# Must pass --workspace: default-members is rbitcoin-node only.
+# Owner: TESTING.md (Mutation testing). Snapshot lists: docs/mutants/.
+
name: mutants
on:
schedule:
@@ -22,13 +26,13 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
submodules: false
fetch-depth: 0
- - uses: dtolnay/rust-toolchain@a5f673d0ba8626c3977bb416a1612774bc82181b
- - uses: taiki-e/install-action@9534c84618278caac52cb373bb164ed464dbd8af
+ - uses: dtolnay/rust-toolchain@a5f673d0ba8626c3977bb416a1612774bc82181b # rustc 1.95.0
+ - uses: taiki-e/install-action@9534c84618278caac52cb373bb164ed464dbd8af # v2.87.11
with:
tool: cargo-mutants@27.1.0
- run: git diff origin/${{ github.base_ref }}.. --unified=0 > git.diff
@@ -44,16 +48,16 @@ jobs:
matrix:
shard: [0, 1, 2, 3, 4, 5, 6, 7]
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
submodules: false
- - uses: dtolnay/rust-toolchain@a5f673d0ba8626c3977bb416a1612774bc82181b
- - uses: taiki-e/install-action@9534c84618278caac52cb373bb164ed464dbd8af
+ - uses: dtolnay/rust-toolchain@a5f673d0ba8626c3977bb416a1612774bc82181b # rustc 1.95.0
+ - uses: taiki-e/install-action@9534c84618278caac52cb373bb164ed464dbd8af # v2.87.11
with:
tool: cargo-mutants@27.1.0
- run: cargo mutants --workspace --shard ${{ matrix.shard }}/8 -j 2
- - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: mutants-${{ matrix.shard }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c7a31f39..27ec43b10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,11 @@ before 1.0).
### Changed
+- **Weekly cargo-mutants:** `mutants.yml` — PR `--workspace --in-diff`
+ advisory (`continue-on-error`); Sunday 8-shard `--workspace` sweep.
+ Must use `--workspace` (`default-members` is node). Snapshot lists:
+ [`docs/mutants/`](docs/mutants/). How to run: [`TESTING.md`](TESTING.md).
+
- **Electrum/Esplora no longer require `--sh-index` to bind.** Address and
scripthash methods return `scripthash index disabled` (Electrum JSON-RPC
error; Esplora HTTP 503). Txid/outpoint/block/fees work. Channel watches
diff --git a/TESTING.md b/TESTING.md
index 34711d7ed..80f5c6c80 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -234,7 +234,7 @@ to us. `regtest_rpc.rs` / `regtest_pad.rs` stay in the denominator.
shipped path that drives the real entry point.
4. Re-run `./scripts/coverage.sh` until the ratio is **≥ 92%**.
-## Structural lints, CRAP, Miri
+## Structural lints, CRAP, Miri, mutants
These do **not** measure operator RSS ([`docs/ibd-memory.md`](./docs/ibd-memory.md)
owns caps). They catch the *shapes* of unbounded heap / leaked tasks, untested
@@ -250,9 +250,10 @@ matches scalar in default tests). Owner: [`docs/quality.md`](./docs/quality.md).
| **cargo-crap** | After LCOV, `./scripts/coverage.sh` calls `./scripts/coverage-crap.sh` (skip if `cargo-crap` missing). `--fail-above --threshold 30`; `.cargo-crap.toml` allowlists today's production CRAP>30 functions (remove a name when it scores ≤30). Dry-run: `CRAP_DRY_RUN=1 ./scripts/coverage-crap.sh`. Self-test: `./scripts/coverage-crap.test.sh` | Rides required `coverage`. No `--fail-regression` (llvm-cov coverage % jitters per function) |
| **coverage ignore / badge** | `./scripts/coverage.test.sh` (filename ignore, Tier A IBD not skipped, 92% floor, Shields JSON). Publish dry-run: `BADGE_DRY_RUN=1 ./scripts/publish-coverage-badge.sh` | `test` job self-test; `coverage` job writes `coverage/badge.json` and, on green `master`, pushes `badges/coverage.json` |
| **Miri** | `./scripts/miri.sh` → `cargo +nightly miri test -p rbitcoin-primitives`. Dry-run: `MIRI_DRY_RUN=1 ./scripts/miri.sh`. Self-test: `./scripts/miri.test.sh` | Nightly `miri.yml` (not required). Never `--workspace` |
+| **cargo-mutants** | Must pass `--workspace` (`Cargo.toml` `default-members` is `rbitcoin-node` only; without `--workspace` the list is ~7 mutants). PR advisory: `git diff origin/.. --unified=0 > git.diff` then `cargo mutants --workspace --in-diff git.diff -j 2`. Weekly: `cargo mutants --workspace --shard N/8 -j 2`. Snapshot of missed/timeouts: [`docs/mutants/`](docs/mutants/). | Weekly `mutants.yml` Sunday 03:00 UTC, 8 shards, 360min cap (not required). `workflow_dispatch`. PR job `mutants-pr` is `continue-on-error` (not a merge gate). First Sunday wall-clock is still a measurement. |
Artifact silos above are unchanged: ast-grep / Miri dry-run / crap dry-run do
-not write `target/`.
+not write `target/`. `mutants.out/` is gitignored.
### Mature-chain fixtures
diff --git a/crates/rbitcoin-primitives/src/script_sigops.rs b/crates/rbitcoin-primitives/src/script_sigops.rs
index c549c589d..c0e3aea56 100644
--- a/crates/rbitcoin-primitives/src/script_sigops.rs
+++ b/crates/rbitcoin-primitives/src/script_sigops.rs
@@ -58,22 +58,6 @@ pub fn script_sigop_count(script: &[u8], accurate: bool) -> u64 {
#[cfg(test)]
mod tests {
-
- #[test]
- fn sigop_count_covers_pushdata_and_truncation() {
- // 21 -= : [4c,2,ac] original 0, mutant i-=1 => 1
- assert_eq!(script_sigop_count(&[0x4c, 2, 0xac], true), 0);
- // 21 *= : [4c,1,ac] original 0, mutant i*=1 => 1
- assert_eq!(script_sigop_count(&[0x4c, 1, 0xac], true), 0);
- // 28 *= : [4d,1,0,ac] original 0, mutant i*=2 => 1
- assert_eq!(script_sigop_count(&[0x4d, 1, 0, 0xac], true), 0);
- // 41 > vs >= vs == : truncated must be 0
- assert_eq!(script_sigop_count(&[0x02, 0x00], true), 0);
- assert_eq!(script_sigop_count(&[0x4c, 2, 0xac, 0xac], true), 0);
- // extra long killer for 21 -= if guard changes
- assert_eq!(script_sigop_count(&[0x4c, 2, 0xac, 0xac, 0xac], true), 1); // PUSHDATA1 2 [ac,ac] + ac => 1
- }
-
use super::*;
#[test]
@@ -113,6 +97,12 @@ mod tests {
script_sigop_count(&[0x4e, 0x01, 0x00, 0x00, 0x00, 0xcd, 0xac], false),
1
);
+ assert_eq!(script_sigop_count(&[0x4c, 2, 0xac], true), 0);
+ assert_eq!(script_sigop_count(&[0x4c, 1, 0xac], true), 0);
+ assert_eq!(script_sigop_count(&[0x4d, 1, 0, 0xac], true), 0);
+ assert_eq!(script_sigop_count(&[0x02, 0x00], true), 0);
+ assert_eq!(script_sigop_count(&[0x4c, 2, 0xac, 0xac], true), 0);
+ assert_eq!(script_sigop_count(&[0x4c, 2, 0xac, 0xac, 0xac], true), 1);
}
#[cfg(miri)]
diff --git a/docs/ORIENT.md b/docs/ORIENT.md
index 7173572f4..6e3e79a88 100644
--- a/docs/ORIENT.md
+++ b/docs/ORIENT.md
@@ -30,6 +30,7 @@ Before the first edit in a crate, read `crates//AGENTS.md` when that file
| Which head file (tx / header / scripthash) | [`heads.md`](./heads.md) |
| Crash, tip-as-commit, kill-9 | [`crash-recovery.md`](./crash-recovery.md) |
| Tests, budgets, coverage, fixtures | [`../TESTING.md`](../TESTING.md) |
+| cargo-mutants (weekly sweep, PR advisory) | [`../TESTING.md`](../TESTING.md) (Mutation testing); snapshot [`mutants/`](./mutants/) |
| Multi-step plan (Red → Green → Refactor) | [`how-we-plan.md`](./how-we-plan.md) |
| cargo / clippy / deny / rustc logs (do not load into the session) | [`how-we-plan.md`](./how-we-plan.md) (Agent RAM) |
| `rearden-grok[bot]` operator VM (ignore unless that identity) | [`../rearden-vm-HOST.md`](../rearden-vm-HOST.md) |
diff --git a/docs/README.md b/docs/README.md
index 49b06d2a8..56067c445 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -58,6 +58,7 @@ into `AGENTS.md`.
| [`errata.md`](./errata.md) | Known one-off store/confirm quirks. |
| [`peer-clients.md`](./peer-clients.md) | Hornet Node and satd: what to steal (tests/ideas) and what not to copy. Ranked items stay here; do not copy into quality.md. |
| [`lightning.md`](./lightning.md) | CLN and LDK as Bitcoin backends: five `bcli` calls, Esplora/Electrum chain sync, `--sh-index` API matrix. Not LND. |
+| [`mutants/`](./mutants/) | Generated cargo-mutants missed/timeout snapshot. How to run / CI: [`TESTING.md`](../TESTING.md). Not a quality backlog. |
| [`external_findings/`](./external_findings/) | Numbered audit reports + regression pointers. Do not flatten into CHANGELOG. |
## Root (stay at root)
diff --git a/docs/mutants/missed.md b/docs/mutants/missed.md
index f06fee421..8dac2ddc6 100644
--- a/docs/mutants/missed.md
+++ b/docs/mutants/missed.md
@@ -1,11 +1,19 @@
-# Mutants Baseline
+# cargo-mutants snapshot
-> Generated: 2026-09-20 16:14 ET · Runner: M4 mini · consensus 2026-09-19 night → 2026-09-20 morning (9h, 2518 tested) · primitives 2026-09-20 ~15:00 ET (16m, 292 tested)
+How to run and what CI does: [`../../TESTING.md`](../../TESTING.md)
+(Mutation testing). This directory is a **generated** missed/timeout list
+from a host run, not the quality roadmap
+([`../quality.md`](../quality.md)).
+
+Generated: 2026-09-20 16:14 ET. Host: M4 mini. Consensus 2026-09-19 night
+→ 2026-09-20 morning (9h, 2518 tested). Primitives 2026-09-20 ~15:00 ET
+(16m, 292 tested).
| Crate | Tested | Missed | Timeout | Details |
| :--- | ---: | ---: | ---: | :--- |
| `rbitcoin-primitives` | 292 | 4 | 8 | [./rbitcoin-primitives.md](./rbitcoin-primitives.md) |
| `rbitcoin-consensus` | 2518 | 304 | 38 | [./rbitcoin-consensus.md](./rbitcoin-consensus.md) |
-### Top files - consensus
+### Top files — consensus
+
- `block/mod.rs` 65 · `script/interpreter.rs` 57 · `silent_payments.rs` 50 · `confirm_run/write.rs` 23 · `confirm_run/pin.rs` 22
diff --git a/docs/mutants/rbitcoin-consensus.md b/docs/mutants/rbitcoin-consensus.md
index 4d0d72b7f..d08d0c732 100644
--- a/docs/mutants/rbitcoin-consensus.md
+++ b/docs/mutants/rbitcoin-consensus.md
@@ -1,4 +1,6 @@
-# rbitcoin-consensus Mutants - Full Specifics Index
+# rbitcoin-consensus mutants (generated)
+
+Owner: [`../../TESTING.md`](../../TESTING.md). Index: [`missed.md`](./missed.md).
Generated from 9h run: 304 missed + 38 timeouts = 342 entries
diff --git a/docs/mutants/rbitcoin-primitives.md b/docs/mutants/rbitcoin-primitives.md
index 9c255ec74..583c8d94c 100644
--- a/docs/mutants/rbitcoin-primitives.md
+++ b/docs/mutants/rbitcoin-primitives.md
@@ -1,4 +1,6 @@
-# rbitcoin-primitives Mutants - Full Specifics Index
+# rbitcoin-primitives mutants (generated)
+
+Owner: [`../../TESTING.md`](../../TESTING.md). Index: [`missed.md`](./missed.md).
Generated from 16m run: 4 missed + 8 timeouts = 12 entries
diff --git a/docs/quality.md b/docs/quality.md
index 7447a1bf1..643b5e571 100644
--- a/docs/quality.md
+++ b/docs/quality.md
@@ -8,7 +8,8 @@ in [`CHANGELOG.md`](../CHANGELOG.md). 1.0 product gates:
**Last reaudit:** 2026-09-17. Schema **24**. Core functional **75**
`test_runner` jobs (**69** inventory `run`; Core expands transport twins and
`wallet_txn_*` flags) / **198** `skip`. Findings **001–023** fixed. Nightly
-fuzz **20** jobs. Previous: 2026-09-15.
+fuzz **20** jobs. Weekly mutants **8** shards (not required; owner
+[`TESTING.md`](../TESTING.md)). Previous: 2026-09-15.
| Section | Purpose |
|---------|---------|