From fda94f034ac2f4949d6c03229b87d2fc3b9e444a Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 20 Jul 2026 00:44:29 +0200 Subject: [PATCH 1/4] Document mutation-testing workflow contract tests Add a "Workflow contract tests" subsection under the existing "Mutation testing" section in the developer guide, describing the caller inputs, the local `make test-workflow-contracts` command, what the pytest suite asserts, and why it deliberately omits a `uses:` SHA check. --- docs/developers-guide.md | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 2d5c289..90b091e 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -69,6 +69,56 @@ and the tracking issue. The `mutants` dev-dependency exists solely so these test-only attributes resolve. Do not skip mutants that can be exercised by the workflow's feature set; strengthen the relevant tests instead. +### Workflow contract tests + +`.github/workflows/mutation-testing.yml` is a thin caller of the shared +reusable workflow `leynos/shared-actions/.github/workflows/mutation-cargo.yml`. +The heavy lifting — running `cargo-mutants` and summarizing survivors — lives +in `shared-actions`; this repository carries only declarative configuration. +The run is informational only: it never gates a pull request, and survivors +are reported through the job summary and downloadable artefacts for triage +into tests rather than enforced as a blocking check. + +The caller currently sets three inputs: + +- `exclude-globs: "src/test_support.rs"` — keeps survivors from the unit-test + scaffolding module out of the report, since they are noise rather than + genuine test gaps. +- `extra-args: "--all-features"` — mirrors the repository's canonical test + baseline (`make test` runs with all features enabled), so feature-gated code + is compiled and exercised against mutants. +- `setup-commands` — pins `PG_PASSWORD` for the embedded PostgreSQL cluster. + The `pg-embed-setup-unpriv` library otherwise generates a fresh random + superuser password per run while cluster state persists between runs, so a + second plain `cargo test` in the same job would fail to authenticate; a + fixed password keeps every per-mutant run consistent with the baseline run. + +Every other input keeps the shared workflow's default, including `paths`, +which this repository does not set. + +`tests/workflow_contracts/mutation_testing_test.py` parses the caller with +PyYAML and pins the shape it must uphold, failing the pull request when the +caller drifts rather than letting the breakage surface only in a scheduled +run. Run it locally with `make test-workflow-contracts` (this wraps `uv run +--with 'pytest>=8' --with 'pyyaml>=6' pytest tests/workflow_contracts -q`). +The test validates: + +- job permissions are exactly least-privilege (`contents: read`, `id-token: + write`), and the workflow-level default token scope is empty; +- `concurrency` serializes runs per ref (`cancel-in-progress: false`); +- the triggers keep the daily 09:35 UTC schedule and a plain + `workflow_dispatch` with no legacy branch input; and +- the `with:` block carries exactly the three inputs above and no others. + +Note what the test deliberately does not assert: it has no check on the +`uses:` line itself, so it neither pins the reusable workflow's commit SHA nor +merely shape-checks it as a 40-character hex string. That gap is intentional +rather than an oversight — as the section above notes, Dependabot manages the +pinned SHA, and duplicating it in a test constant would require every +Dependabot bump to edit the test in lockstep. The trade-off is that the +contract test alone will not catch the pin being repointed at a branch or tag; +that risk is accepted in exchange for frictionless SHA bumps. + ## Compile-fail UI tests Compile-time contracts for macros and type-level behaviour are covered by the From 08bc3ff18f850c927b316997b96f8040c05f7d4e Mon Sep 17 00:00:00 2001 From: leynos Date: Thu, 23 Jul 2026 01:01:04 +0200 Subject: [PATCH 2/4] Narrow documented workflow trigger contract Describe only the schedule and dispatch properties asserted by the workflow contract test. Avoid implying that additional triggers or dispatch inputs would fail CI. --- docs/developers-guide.md | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 90b091e..5a6159a 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -73,11 +73,11 @@ workflow's feature set; strengthen the relevant tests instead. `.github/workflows/mutation-testing.yml` is a thin caller of the shared reusable workflow `leynos/shared-actions/.github/workflows/mutation-cargo.yml`. -The heavy lifting — running `cargo-mutants` and summarizing survivors — lives -in `shared-actions`; this repository carries only declarative configuration. -The run is informational only: it never gates a pull request, and survivors -are reported through the job summary and downloadable artefacts for triage -into tests rather than enforced as a blocking check. +The heavy lifting — running `cargo-mutants` and summarizing survivors — lives in +`shared-actions`; this repository carries only declarative configuration. The +run is informational only: it never gates a pull request, and survivors are +reported through the job summary and downloadable artefacts for triage into +tests rather than enforced as a blocking check. The caller currently sets three inputs: @@ -90,34 +90,34 @@ The caller currently sets three inputs: - `setup-commands` — pins `PG_PASSWORD` for the embedded PostgreSQL cluster. The `pg-embed-setup-unpriv` library otherwise generates a fresh random superuser password per run while cluster state persists between runs, so a - second plain `cargo test` in the same job would fail to authenticate; a - fixed password keeps every per-mutant run consistent with the baseline run. + second plain `cargo test` in the same job would fail to authenticate; a fixed + password keeps every per-mutant run consistent with the baseline run. -Every other input keeps the shared workflow's default, including `paths`, -which this repository does not set. +Every other input keeps the shared workflow's default, including `paths`, which +this repository does not set. `tests/workflow_contracts/mutation_testing_test.py` parses the caller with PyYAML and pins the shape it must uphold, failing the pull request when the -caller drifts rather than letting the breakage surface only in a scheduled -run. Run it locally with `make test-workflow-contracts` (this wraps `uv run ---with 'pytest>=8' --with 'pyyaml>=6' pytest tests/workflow_contracts -q`). +caller drifts rather than letting the breakage surface only in a scheduled run. +Run it locally with `make test-workflow-contracts` (this wraps +`uv run --with 'pytest>=8' --with 'pyyaml>=6' pytest tests/workflow_contracts -q`). The test validates: -- job permissions are exactly least-privilege (`contents: read`, `id-token: - write`), and the workflow-level default token scope is empty; +- job permissions are exactly least-privilege (`contents: read`, + `id-token: write`), and the workflow-level default token scope is empty; - `concurrency` serializes runs per ref (`cancel-in-progress: false`); -- the triggers keep the daily 09:35 UTC schedule and a plain - `workflow_dispatch` with no legacy branch input; and +- the daily 09:35 UTC schedule remains, and `workflow_dispatch` is present + without the legacy `branch` input; and - the `with:` block carries exactly the three inputs above and no others. -Note what the test deliberately does not assert: it has no check on the -`uses:` line itself, so it neither pins the reusable workflow's commit SHA nor -merely shape-checks it as a 40-character hex string. That gap is intentional -rather than an oversight — as the section above notes, Dependabot manages the -pinned SHA, and duplicating it in a test constant would require every -Dependabot bump to edit the test in lockstep. The trade-off is that the -contract test alone will not catch the pin being repointed at a branch or tag; -that risk is accepted in exchange for frictionless SHA bumps. +Note what the test deliberately does not assert: it has no check on the `uses:` +line itself, so it neither pins the reusable workflow's commit SHA nor merely +shape-checks it as a 40-character hex string. That gap is intentional rather +than an oversight — as the section above notes, Dependabot manages the pinned +SHA, and duplicating it in a test constant would require every Dependabot bump +to edit the test in lockstep. The trade-off is that the contract test alone +will not catch the pin being repointed at a branch or tag; that risk is +accepted in exchange for frictionless SHA bumps. ## Compile-fail UI tests From ea42586735e67259149cc59c7094b37a9046c914 Mon Sep 17 00:00:00 2001 From: leynos Date: Thu, 23 Jul 2026 01:01:54 +0200 Subject: [PATCH 3/4] Refresh generated Oxford spelling variants Record the generated polymerize-family mappings so the spelling configuration remains synchronized with its source vocabulary. --- typos.toml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/typos.toml b/typos.toml index 9f11fbe..7e18720 100644 --- a/typos.toml +++ b/typos.toml @@ -1700,6 +1700,24 @@ extend-ignore-re = [ "pluralizers" = "pluralizers" "pluralizes" = "pluralizes" "pluralizing" = "pluralizing" +"polymerisable" = "polymerizable" +"polymerisation" = "polymerization" +"polymerisations" = "polymerizations" +"polymerise" = "polymerize" +"polymerised" = "polymerized" +"polymeriser" = "polymerizer" +"polymerisers" = "polymerizers" +"polymerises" = "polymerizes" +"polymerising" = "polymerizing" +"polymerizable" = "polymerizable" +"polymerization" = "polymerization" +"polymerizations" = "polymerizations" +"polymerize" = "polymerize" +"polymerized" = "polymerized" +"polymerizer" = "polymerizer" +"polymerizers" = "polymerizers" +"polymerizes" = "polymerizes" +"polymerizing" = "polymerizing" "popularisable" = "popularizable" "popularisation" = "popularization" "popularisations" = "popularizations" From 1a38a12cbd879fdbfdffd99247ea879ed652dd9b Mon Sep 17 00:00:00 2001 From: leynos Date: Sat, 15 Aug 2026 17:13:31 +0200 Subject: [PATCH 4/4] Enforce mutation workflow SHA shape Validate that the reusable mutation workflow stays pinned to a full commit SHA without coupling the test to Dependabot-managed revision values. Preserve inline-code spelling exceptions through configuration regeneration. --- docs/developers-guide.md | 14 ++++++-------- .../workflow_contracts/mutation_testing_test.py | 16 ++++++++++++++++ typos.local.toml | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 5a6159a..ec8410e 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -108,16 +108,14 @@ The test validates: - `concurrency` serializes runs per ref (`cancel-in-progress: false`); - the daily 09:35 UTC schedule remains, and `workflow_dispatch` is present without the legacy `branch` input; and +- `uses:` names the shared `mutation-cargo.yml` workflow and ends with a + 40-character hexadecimal commit SHA; and - the `with:` block carries exactly the three inputs above and no others. -Note what the test deliberately does not assert: it has no check on the `uses:` -line itself, so it neither pins the reusable workflow's commit SHA nor merely -shape-checks it as a 40-character hex string. That gap is intentional rather -than an oversight — as the section above notes, Dependabot manages the pinned -SHA, and duplicating it in a test constant would require every Dependabot bump -to edit the test in lockstep. The trade-off is that the contract test alone -will not catch the pin being repointed at a branch or tag; that risk is -accepted in exchange for frictionless SHA bumps. +The test validates only the SHA's shape, not its value. Dependabot continues to +manage the exact pin, so its updates do not require a matching test change; the +contract merely prevents the reusable workflow from being repointed at a +branch, tag, or abbreviated revision. ## Compile-fail UI tests diff --git a/tests/workflow_contracts/mutation_testing_test.py b/tests/workflow_contracts/mutation_testing_test.py index 97da37c..5965d1e 100644 --- a/tests/workflow_contracts/mutation_testing_test.py +++ b/tests/workflow_contracts/mutation_testing_test.py @@ -13,6 +13,7 @@ from __future__ import annotations +import re from pathlib import Path import yaml @@ -38,6 +39,11 @@ } +USES_RE = re.compile( + r"^leynos/shared-actions/.github/workflows/mutation-cargo\.yml@[0-9A-Fa-f]{40}$" +) + + def _load() -> dict[str, object]: """Parse the workflow file.""" return yaml.safe_load(WORKFLOW_PATH.read_text(encoding="utf-8")) @@ -109,6 +115,16 @@ def test_triggers_keep_schedule_and_plain_dispatch() -> None: ) +def test_mutation_job_uses_a_pinned_shared_workflow() -> None: + """The reusable workflow reference keeps a full commit-SHA pin.""" + uses = _mutation_job(_load()).get("uses") + assert isinstance(uses, str), "jobs.mutation.uses must be a string" + assert USES_RE.fullmatch(uses), ( + "jobs.mutation.uses must reference mutation-cargo.yml at a " + f"40-character commit SHA, got {uses!r}" + ) + + def test_with_block_carries_the_caller_configuration() -> None: """The caller passes exactly the scaffolding exclusion and feature args.""" with_block = _mutation_job(_load()).get("with") diff --git a/typos.local.toml b/typos.local.toml index 269b761..61076b7 100644 --- a/typos.local.toml +++ b/typos.local.toml @@ -12,7 +12,7 @@ accepted = ["Center"] [words.corrections] [patterns] -ignore = [] +ignore = ["`[^`\\n]+`"] [files] exclude = []