Skip to content

feat(hash): three-tier identity + Tier-0 cache + on-demand canonical hash (closes #151 #155, advances #157) #3

feat(hash): three-tier identity + Tier-0 cache + on-demand canonical hash (closes #151 #155, advances #157)

feat(hash): three-tier identity + Tier-0 cache + on-demand canonical hash (closes #151 #155, advances #157) #3

Workflow file for this run

# cargo-mutants per-PR mutation testing — Linux-only, observability-only.
# See spec: docs/superpowers/specs/2026-04-24-cargo-mutants-design.md
#
# WHY a separate workflow (not added to ci.yml's matrix): mutation
# testing is CPU-heavy + slow (minutes per file); decoupling lets it
# fail-or-skip independently of the main `just ci` matrix. PR contributors
# see two checks instead of one. Failure of mutants.yml does NOT block
# merge during slice 1 (continue-on-error swallows non-zero exit).
name: mutants
# WHY workflow_dispatch added (2026-04-25): allows on-demand "real deal"
# workspace-wide mutation runs (no --in-diff filter) — much longer than
# per-PR mode (HOURS, not 1-2 minutes). The per-PR `pull_request:` trigger
# stays as-is; the run-step branches on github.event_name to choose mode.
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
mutants:
name: cargo mutants
runs-on: ubuntu-latest
# WHY no `if:` gate: both pull_request AND workflow_dispatch run the job;
# the run-step itself branches on github.event_name to pick --in-diff vs
# workspace-wide mode. Adding an event-name gate here would skip
# workflow_dispatch entirely (the prior `== 'pull_request'` was a leftover
# from PR-only-mode and would now block manual runs).
steps:
- uses: actions/checkout@v4
with:
# WHY fetch-depth: 0 — `git diff origin/<base>..` requires the
# base branch in local history; depth 0 fetches the full graph.
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# WHY taiki-e/install-action (NOT cargo-binstall): this workflow
# runs on ubuntu-latest only; the BASH_FUNC_* leak that broke
# taiki-e/install-action on windows-latest (GH #137) does NOT
# affect Linux. Multi-tool comma syntax fetches both binaries in
# one step.
- uses: taiki-e/install-action@v2
with:
tool: cargo-mutants,cargo-nextest
# WHY Tauri Linux deps even though crates/desktop is excluded from
# mutation: `cargo metadata` (which cargo-mutants invokes
# internally) resolves the WHOLE workspace including
# perima-desktop's transitive deps. Without GTK headers, metadata
# resolution fails before any mutation happens.
- name: Install Tauri Linux deps
run: |
sudo apt-get update -q
sudo apt-get install -yq \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf
# WHY `| tee git.diff` (vs `> git.diff`): tee echoes the diff into
# the CI log so reviewers can see what cargo-mutants will operate
# on without downloading the artifact. Matches the canonical
# mutants.rs/in-diff example.
#
# WHY only on pull_request: workflow_dispatch has no base branch to
# diff against; the run-step's workspace mode skips this file.
- name: Compute diff against base branch
if: github.event_name == 'pull_request'
run: git diff origin/${{ github.base_ref }}.. | tee git.diff
# WHY continue-on-error: per spec D-4, observability-only mode.
# cargo-mutants exits 2 when mutants survive, 3 on timeout, 4 on
# baseline failure. Without continue-on-error the very first PR
# (or workspace run) with a survivor blocks the workflow + defeats
# the observability-first calibration goal. Precedent: kani.yml
# uses the same pattern.
#
# WHY branch on event_name: pull_request → --in-diff mode (fast,
# mutates only changed code per spec slice 1 D-2). workflow_dispatch
# → workspace-wide (HOURS, baseline-collection mode). Same workflow
# file serves both cadences.
- name: cargo mutants
continue-on-error: true
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
cargo mutants --no-shuffle -vV --in-diff git.diff
else
cargo mutants --no-shuffle -vV
fi
- name: Upload mutants.out
if: always()
uses: actions/upload-artifact@v4
with:
name: mutants-out
path: mutants.out