S-04b finishes the last acceptance criterion split off S-04 (#5): a captured, enforced mutation-score baseline for the ACL. CLAUDE.md §3/§5 make "mutation (ratchet)" a Definition-of-Done gate for every slice, but no baseline existed — so this establishes the repo-wide one.
Stryker.NET pinned as a local dotnet tool (.config/dotnet-tools.json, v4.15.0) — reproducible locally and in CI, no global install.
Solution mode on Acl.slnx (services/acl/stryker-config.json): mutates Acl.Application + Acl.Infrastructure; the untested Acl.Api is skipped.
make mutation target, wired into make ciand a parallel Gitea Actions mutation job.
Closes#47 Closes#51 (the adr-proposal — ADR-0005 lands in this PR)
Definition of Done
Linked Gitea issue (above).
Failing test committed before the implementation. (See notes — TDD nuance for a tooling slice.)
Implementation makes the test pass; refactor commit if structure improved.
Conventional Commits referencing the issue (refs #NN).
CI green — make ci (lint, build, unit, mutation) green locally; compose-smoke unchanged by this slice and CI-gated.
docker compose up from a fresh clone reaches green health checks within 3 minutes. (Unchanged — no infra touched.)
Docs updated if behaviour, contracts, or operations changed. (ADR-0005, docs/runbooks/ci.md, docs nav.)
ADR added in docs/architecture/ if a non-obvious decision was made. (ADR-0005.)
Demo note in docs/demo-script.md if user-visible. (N/A — not user-visible.)
Notes for reviewers
TDD nuance: a CI/tooling slice has no natural xUnit red→green. The "red" was the mutation run itself — 35% with 12 survivors; the "green" is killing them to 95% (commit 10816f5) and the ratchet (break 90%) failing CI on regression. Tests were strengthened, not padded.
Ratchet proven to bite:break 90% → Stryker exits 0 at 95%; a temp floor of 96% exits non-zero with "score below threshold break". Later slices ratchet up, never down (§5).
One accepted survivor: a mutation of the empty-response exception message string — an equivalent mutant (message-text assertions are brittle; type + control flow unchanged).
## What & why
S-04b finishes the last acceptance criterion split off S-04 (#5): a captured, enforced **mutation-score baseline** for the ACL. CLAUDE.md §3/§5 make "mutation (ratchet)" a Definition-of-Done gate for every slice, but no baseline existed — so this establishes the repo-wide one.
- **Stryker.NET** pinned as a local dotnet tool (`.config/dotnet-tools.json`, v4.15.0) — reproducible locally and in CI, no global install.
- **Solution mode** on `Acl.slnx` (`services/acl/stryker-config.json`): mutates `Acl.Application` + `Acl.Infrastructure`; the untested `Acl.Api` is skipped.
- `make mutation` target, wired into `make ci` **and** a parallel Gitea Actions `mutation` job.
- **Baseline:** Stryker exposed thin ACL tests (35%). Strengthened them honestly — CRS headers, null guards, error/empty-body paths, JWT header+claims decode — raising the score to **95%**. Enforced `break` threshold **90%** (one-mutant headroom).
- **ADR-0005** records the decision (proposed in #51).
Closes #47
Closes #51 (the adr-proposal — ADR-0005 lands in this PR)
## Definition of Done
- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation. *(See notes — TDD nuance for a tooling slice.)*
- [x] Implementation makes the test pass; refactor commit if structure improved.
- [x] Conventional Commits referencing the issue (`refs #NN`).
- [x] CI green — `make ci` (lint, build, unit, mutation) green locally; `compose-smoke` unchanged by this slice and CI-gated.
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes. *(Unchanged — no infra touched.)*
- [x] Docs updated if behaviour, contracts, or operations changed. *(ADR-0005, `docs/runbooks/ci.md`, docs nav.)*
- [x] ADR added in `docs/architecture/` if a non-obvious decision was made. *(ADR-0005.)*
- [ ] Demo note in `docs/demo-script.md` if user-visible. *(N/A — not user-visible.)*
## Notes for reviewers
- **TDD nuance:** a CI/tooling slice has no natural xUnit red→green. The "red" was the mutation run itself — 35% with 12 survivors; the "green" is killing them to 95% (commit `10816f5`) and the ratchet (`break` 90%) failing CI on regression. Tests were strengthened, not padded.
- **Ratchet proven to bite:** `break` 90% → Stryker exits 0 at 95%; a temp floor of 96% exits non-zero with "score below threshold break". Later slices ratchet **up**, never down (§5).
- **One accepted survivor:** a mutation of the empty-response *exception message string* — an equivalent mutant (message-text assertions are brittle; type + control flow unchanged).
- **Sibling follow-up:** #46 (S-04a, real-OpenZaak Testcontainers integration test) remains open.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add a tool manifest pinning dotnet-stryker 4.15.0 so `make mutation` runs
the same mutation tester locally and in CI from a fresh clone (`dotnet tool
restore`), with no global install. Ignore the generated StrykerOutput/ report
directory.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stryker exposed thin ACL tests (35% mutation score): the suite never asserted
the geo CRS headers, the ArgumentNullException guards, the non-success and
empty-body error paths, or the structure of the minted ZGW JWT — so mutating
any of those survived.
Strengthen the unit tests to kill those mutants:
- assert Accept-Crs / Content-Crs are EPSG:4326,
- assert OpenZaakAsync rejects a null request/registration without calling out,
- assert a non-2xx response throws and an empty body throws InvalidOperationException,
- decode the Bearer token and assert the HS256 header + acl identity claims.
Raises the ACL mutation score to 95%. The one remaining survivor mutates only
the exception *message* text (an equivalent mutant — message strings are not
worth a brittle assertion).
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Configure Stryker.NET for the ACL in solution mode (Acl.slnx), so both
Acl.Application and Acl.Infrastructure — the two projects under test — are
mutated while Acl.Api (untested) is skipped. Record the repo-wide mutation
baseline as the ratchet (CLAUDE.md §5): observed score 95%, enforced break
threshold 90% (one-mutant headroom over the ~20-mutant surface). The ACL is the
first service with branching logic, so it sets the baseline; later slices
ratchet it up deliberately, never down.
Add a `mutation` make target (`dotnet tool restore` + `dotnet stryker`) and wire
it into the `make ci` aggregate, keeping `make ci` an exact mirror of the
pipeline.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `mutation` job mirroring the unit job (checkout + pinned setup-dotnet,
then `make mutation`). It runs in parallel with lint/build/unit/compose-smoke
and gates merges on the ACL mutation baseline (CLAUDE.md §5/§15). The job calls
the same make target developers run, so the pipeline stays a mirror of `make ci`.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the decision to adopt Stryker.NET (pinned local tool, solution mode on
Acl.slnx) and to set the first repo-wide mutation baseline on the ACL: observed
95%, enforced break threshold 90%. Document the ratchet, local run, and report
location in the CI runbook; add the ADR to the docs nav.
Proposed in #51 (adr-proposal). Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not
added this to the Iteration 1 — Walking Skeleton milestone 2026-06-25 13:01:37 +00:00
Add an upload-artifact step to the mutation job so the ACL mutation report is
downloadable from the run summary. `if: always()` uploads it even when the
ratchet fails — exactly when the survivors matter. A glob handles Stryker's
timestamped output directory. First use of actions/upload-artifact (@v4, pinned);
Gitea 1.25.x supports it. Document it in the CI runbook.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The artifact step failed the mutation job: upload-artifact@v4 bundles
@actions/artifact v2, which hard-aborts on any non-github.com server ("not
supported on GHES"), even though Gitea 1.25 stores artifacts fine. @v3 uses the
older protocol Gitea speaks and has no GHES guard — a drop-in swap (same inputs).
Document it as gotcha §4 and correct the CI runbook note.
Refs #47.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not
merged commit fd5fa5ac3c into main2026-06-29 09:00:30 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
S-04b finishes the last acceptance criterion split off S-04 (#5): a captured, enforced mutation-score baseline for the ACL. CLAUDE.md §3/§5 make "mutation (ratchet)" a Definition-of-Done gate for every slice, but no baseline existed — so this establishes the repo-wide one.
.config/dotnet-tools.json, v4.15.0) — reproducible locally and in CI, no global install.Acl.slnx(services/acl/stryker-config.json): mutatesAcl.Application+Acl.Infrastructure; the untestedAcl.Apiis skipped.make mutationtarget, wired intomake ciand a parallel Gitea Actionsmutationjob.breakthreshold 90% (one-mutant headroom).Closes #47
Closes #51 (the adr-proposal — ADR-0005 lands in this PR)
Definition of Done
refs #NN).make ci(lint, build, unit, mutation) green locally;compose-smokeunchanged by this slice and CI-gated.docker compose upfrom a fresh clone reaches green health checks within 3 minutes. (Unchanged — no infra touched.)docs/runbooks/ci.md, docs nav.)docs/architecture/if a non-obvious decision was made. (ADR-0005.)docs/demo-script.mdif user-visible. (N/A — not user-visible.)Notes for reviewers
10816f5) and the ratchet (break90%) failing CI on regression. Tests were strengthened, not padded.break90% → Stryker exits 0 at 95%; a temp floor of 96% exits non-zero with "score below threshold break". Later slices ratchet up, never down (§5).🤖 Generated with Claude Code
The artifact step failed the mutation job: upload-artifact@v4 bundles @actions/artifact v2, which hard-aborts on any non-github.com server ("not supported on GHES"), even though Gitea 1.25 stores artifacts fine. @v3 uses the older protocol Gitea speaks and has no GHES guard — a drop-in swap (same inputs). Document it as gotcha §4 and correct the CI runbook note. Refs #47. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>