From 5998390fb410893f3725890769540e6c1a15fbbe Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 20 Jul 2026 00:44:22 +0200 Subject: [PATCH] Document mutation-testing workflow contract tests Add a section to the developers' guide explaining the caller workflow's purpose, its two run modes, the with: inputs it passes, the SHA-pinning style of the reusable-workflow reference, and how to run the contract test locally, so contributors understand why the test exists and how to reproduce it without reading the workflow YAML and test source directly. --- docs/developers-guide.md | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index d32a002..4112872 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -21,3 +21,60 @@ tracked file. The focused builder refreshes a valid cache only when its bundled authority is newer; the repository retains phrase enforcement in `scripts/typos_rollout_check.py` because Typos cannot represent exact phrase corrections faithfully. + +## Mutation-testing workflow contract tests + +This repository runs scheduled, informational mutation testing through a thin +caller workflow, [`.github/workflows/mutation-testing.yml`](../.github/workflows/mutation-testing.yml), +which delegates to the shared reusable workflow +`leynos/shared-actions/.github/workflows/mutation-cargo.yml`. The heavy lifting +— running `cargo-mutants`, sharding, and summarizing survivors — lives in +`shared-actions`; this repository carries only declarative configuration. The +run is **informational only**: it never gates a pull request. Survivors are +reported through the job summary and downloadable artefacts so they can be +triaged into tests, not enforced as a blocking check. + +The workflow runs in two modes. A **daily schedule** fires a change-scoped run +that mutates only the source files touched within the detection window, so +quiet days are cheap no-ops. A **manual dispatch** (the Actions "Run workflow" +control) mutates the whole workspace, fanned out across shards; select a +branch in that control to exercise a feature branch. + +The caller passes a small set of configuration inputs, each carrying intent: + +- `paths` — the change-detection globs (`src/,crates/`) that decide whether a + scheduled run has anything to mutate: the root `comenq-lib` crate lives + under `src/`, and the `comenq` client and `comenqd` daemon crates live under + `crates/`. +- `exclude-globs` — `test-support/**` and `crates/test-utils/**`, the + test-helper scaffolding whose surviving mutants are noise rather than + genuine test gaps, kept out of the survivors table. +- `extra-args` — `--workspace --all-features --test-workspace=true`, so every + workspace member is mutated against the full workspace test run, matching + the CI baseline (`make test` runs workspace-wide with `--all-features`); + without `--workspace`, cargo-mutants would mutate only `comenq-lib` and skip + the `crates/` members, since the root manifest is itself a package. + +The `uses:` reference pins the shared workflow to a full 40-character commit +SHA rather than a branch or tag, so a force-push upstream cannot silently +change what runs here. The contract test asserts only that the pin is a full +commit SHA, not a particular value, so Dependabot bumps it automatically +without any accompanying test edit. + +Because the caller is configuration rather than code, a contract test pins the +shape it must uphold, failing the pull request when the caller drifts — +repointing the pin at a branch, widening the token scope, or dropping a +configuration input — rather than letting the breakage surface only in a +scheduled run. Run it locally with `make test-workflow-contracts`. The test +validates: + +- the `uses:` reference targets `mutation-cargo.yml` pinned to a full commit + SHA; +- the `with:` block carries exactly the expected configuration (the paths, + excludes, and workspace arguments above); +- job permissions are least-privilege (`contents: read`, `id-token: write`) + and the workflow-level default token scope is empty; +- `concurrency` serializes runs per ref without cancelling one in progress; + and +- the triggers keep the daily schedule and a plain `workflow_dispatch` with no + legacy branch input.