diff --git a/docs/developers-guide.md b/docs/developers-guide.md index dcb34858..19c6f8f1 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -1182,6 +1182,79 @@ If a workflow's behaviour genuinely depends on a feature only present from a particular commit onwards, express that as a comment or a changelog note, not as a test assertion on the SHA string. +## 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-mutmut.yml`. The heavy +lifting — running `mutmut` 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. Only the Python package +is enrolled: the pinned `mutation-cargo.yml` reusable workflow always fans a +repository-root `cargo-mutants` target out for `workflow_dispatch` full runs, +and Cuprum has no repository-root `Cargo.toml` (the Rust workspace manifest +lives at `rust/Cargo.toml`), so a Rust job cannot ride the shared workflow at +this pin. The mutation targets and test selection are configured separately in +`[tool.mutmut]` in `pyproject.toml` (`source_paths`, +`pytest_add_cli_args_test_selection`, `do_not_mutate`). + +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 package; select a branch in that control to +exercise a feature branch. + +The caller passes two configuration inputs: + +- `paths` — `cuprum/`, the change-detection glob that decides whether a + scheduled run has anything to mutate. The flat repository layout puts the + mutable source directly at the repository root, so change detection watches + the package itself. +- `module-prefix-strip` — the empty string, because the flat layout has no + `src/` prefix to strip before module-glob translation. + +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 (see +[Workflow pins and Dependabot](#workflow-pins-and-dependabot) above for the +shape-only rationale). + +### Workflow contract tests + +Because the caller is configuration rather than code, `tests/test_workflow_contract.py` +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. Unlike some sibling repositories, this module has no +`skipif` guard: `.github/` is listed in `[tool.mutmut].also_copy`, so the +workflow file is present inside mutmut's sandbox and the contract test runs +there too. Run it locally with: + +```bash +uv run --with pytest --with pyyaml pytest tests/test_workflow_contract.py -q +``` + +There is no dedicated Makefile target for this test; it also falls outside +`PYTEST_TARGETS`, the glob list `make test` uses, so it must be run directly +with the command above (or as part of a full mutmut pass). The test +validates: + +- the `uses:` reference targets `mutation-mutmut.yml` pinned to a full commit + SHA; +- the `with:` block carries exactly `{"paths": "cuprum/", + "module-prefix-strip": ""}`; +- 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 08:20 UTC schedule and a plain + `workflow_dispatch` with no inputs. + ## Compile-time UI tests (trybuild) The Rust crate at `rust/cuprum-rust/` uses