Appearance
ADR 0009 — Repository contracts and non-growing quality gates
- Status: Accepted
- Date: 2026-08-20
- Supersedes in part: ADR 0001 analyzer/SCA enforcement details
- Amended: 2026-09-10 — decision 9 pins the SDK feature band instead of the exact patch; see Amendments
Context
The repository could build and test while public package discovery, formatting, warning policy, informational diagnostics and JavaScript dependency advisories drifted independently. The master review at a46f3c6 also found no durable post-merge attestation. A gate is useful only when the same command runs locally and in CI, and documentation is a contract only when every public artifact is discoverable.
Decision
README.mdis the concise entry point;docs/guide/package-overview.mdis the canonical capability/package/template catalog; DocFX generates exact public signatures from allsrcprojects.- Local scripts are the source of truth. CI calls those scripts instead of duplicating their rules in YAML. They now run from
.github/workflows/_quality.ymland_build-test.yml; the Azure stages this ADR was written against were replaced by ADR 0008. - Whitespace and default-severity style diagnostics are zero-tolerance.
Directory.Build.propsenables warnings-as-errors for Release, sodotnet build -c Releaseis the same contract locally and in CI. Generated templates are built outside the repository tree and therefore receive the equivalent command-line property in their smoke test. - Roslyn warning/error diagnostics are zero-tolerance. Informational baseline entries persist only normalized path, rule, severity and count; current positions and messages remain diagnostic output, not unstable identity. New/increased signatures fail, and reduced signatures also fail until the baseline shrinks in the same PR. Updating the baseline is an explicit backlog/governance change.
- NuGet and both npm dependency graphs must contain zero known advisories of any severity. Container scanning is nightly and separately reported.
- PR validation covers
developandmaster; a non-publishing build runs after every merge tomaster; package publication and release documentation deployment remain tag-only. - SonarCloud remains optional analysis/decorating infrastructure. Deterministic repository scripts, not an optional external connection, are the required merge gates. The warning-as-error build runs in its own job, ahead of the optional SonarCloud job, so Sonar's quality-profile diagnostics can never change the required build result.
- Coverlet continues producing Cobertura/OpenCover reports and CI publishes a merged report. Coverage enforcement is deferred to backlog F-19 through F-21 until source paths can be canonicalized and non-regression can be enforced per production assembly.
- The .NET SDK feature band is pinned by
global.json:versionnames the lowest accepted patch androllForward: latestPatchaccepts newer patches of that band only. Feature-band roll-forward stays disabled because analyzer inventories are part of the merge contract and must be identical locally and in CI. (Amended 2026-09-10; the original text pinned the exact patch.)
Consequences
- A broad initial formatting diff is accepted once; subsequent diffs remain small.
- The informational baseline is visible debt and must shrink in the same PR that removes a diagnostic; it may never silently grow or retain a fixed allowance.
- Coverage remains visible in CI/Sonar artifacts but is temporarily reporting-only. Its blocking design and 80% new-code target are explicit deferred backlog, not an accepted lower standard.
- Generated DocFX HTML and coverage outputs stay out of source control.
- Adding a package, template, endpoint or configuration surface requires its public contract documentation in the same PR.
- SDK upgrades are explicit dependency changes that regenerate and justify any analyzer-baseline delta.
Amendments
2026-09-10 — the SDK pin names a feature band, not a patch
The exact pin (10.0.102, rollForward: disable) kept the analyzer inventory deterministic, and it also froze the toolchain: CI and developer machines stayed on runtime 10.0.2 while nine security releases shipped, every developer had to install one specific patch, and the floating mcr.microsoft.com/dotnet/sdk:10.0 image stopped building the performance SUT the day it moved to 10.0.401, breaking the nightly run.
What changes the inventory is the feature band, which carries the compiler and analyzer versions: 10.0.1xx to 10.0.4xx added 574 CA1873 diagnostics and nothing else. Patches of a band are servicing releases. The pin therefore moves to 10.0.401 with rollForward: latestPatch, which actions/setup-dotnet honours by installing the newest patch of the band. If a patch ever alters the inventory, the gate fails with a per-rule summary and the remedy is the regeneration this ADR already requires for SDK upgrades.
The same band change broke the DocFX metadata step, whose bundled compiler cannot load the newer Razor source generator. docs/docfx.json sets UseRazorSourceGenerator=false for metadata only; no published project contains Razor views, and the generated API metadata was verified byte-identical under both SDKs.