Appearance
ADR 0008 — CI on GitHub Actions, with the gates fanned out in parallel
- Status: Accepted
- Date: 2026-08-22
- Supersedes: the platform decision in ADR 0001 (single CI on Azure DevOps). The gate content decided there — SAST, SCA, secret scanning, DAST, mutation testing — is unchanged and still in force.
Context
The git repository moved from Azure Repos (dev.azure.com/grydlab/Gryd.IO) to GitHub (github.com/GRYD-LAB/Gryd.IO). azure-pipelines.yml could no longer be triggered by a push or a pull request, so the CI that ADR 0001 consolidated onto Azure DevOps had nowhere to run.
ADR 0001 chose a single CI on Azure DevOps and deleted .github/. That decision was made when the code lived in Azure Repos; the premise no longer holds. Work items, boards and the package feed stay in Azure DevOps.
The Azure pipeline also carried a constraint worth naming, because it shaped the whole design: the organisation had one parallel job. Its own comment on BuildAndTest reads "the cheap gates are deliberately serialized ahead of it instead of racing it […] Restore the parallel fan-out once the org has more than one parallel job." Every gate therefore waited for the one before it, and a pull request paid the sum of all of them. GitHub-hosted runners give this account 20 concurrent jobs.
Decision
1. GitHub Actions is the CI platform
azure-pipelines.yml is deleted and replaced by five entry-point workflows plus three reusable ones, mapped stage by stage in .github/workflows/README.md. No gate is dropped: branch-flow rules, commitlint, SCA, secret scanning, the tracked-artifact and banned-identifier guards, build, test, coverage, SonarCloud, nightly tests, mutation testing, performance smoke, DAST, NuGet pack/publish and both docs deploys all survive.
2. The gates run in parallel
The chain branch rules → commitlint → security → build becomes a fan-out. Nothing about it was a correctness requirement; it was queue economics on a single agent. A failing commitlint now costs a cancelled build instead of a delayed one, which is the right trade when the build does not have to wait for a free agent.
3. The test suite is sharded
dotnet test Gryd.IO.sln becomes ten parallel jobs over the shards in .github/test-shards.json. Wall-clock is now the slowest shard — the Auth integration tests — instead of every project in sequence.
A hand-maintained list of test projects rots silently: someone adds a test project, forgets the shard file, and CI stays green while never running it. .github/scripts/check-test-shards.sh therefore runs before the matrix and fails when the shards and the repository disagree, in either direction.
4. Packages continue to publish to Azure Artifacts
The feed does not move. The only change is authentication: NuGetAuthenticate@1 used the Azure DevOps build identity, which does not exist on GitHub, so the push authenticates with a PAT (AZURE_ARTIFACTS_PAT) written into a throwaway nuget.config. The repository's own nuget.config starts with <clear />, which would otherwise drop the push source.
5. Reporting is rebuilt, not dropped
PublishTestResults@2 and PublishCodeCoverageResults@2 have no GitHub equivalent. Test results are parsed from the .trx files into the run summary (.github/scripts/trx-summary.py), and the ten per-shard Cobertura files are merged with ReportGenerator into one report and one summary.
6. Branch policy (manual, GitHub settings)
As in ADR 0001, the pipeline exposes the gates and the enforcement is configured outside the repo: Settings → Branches → develop, requiring the PR gate check. Matrix jobs and a reusable workflow's inner jobs cannot be named as required checks, so PR gate exists to aggregate them into a single required name.
Consequences
- Pull request feedback is bounded by the slowest job rather than the sum of the gates, and a broken commit message no longer blocks the build behind it.
- More total CI minutes are consumed: shards rebuild the shared projects they each depend on. This is the trade the 20-job concurrency buys, and it is the intended one.
- The shard list is a new thing to maintain. It is guarded by a script rather than by discipline.
- One new secret (
AZURE_ARTIFACTS_PAT) has to be rotated. Azure's build identity needed no rotation; that convenience does not survive leaving the platform. - Work items and the package feed still live in Azure DevOps, so the two systems have not fully parted ways — only CI has.