Skip to content

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 ​

  1. README.md is the concise entry point; docs/guide/package-overview.md is the canonical capability/package/template catalog; DocFX generates exact public signatures from all src projects.
  2. 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.yml and _build-test.yml; the Azure stages this ADR was written against were replaced by ADR 0008.
  3. Whitespace and default-severity style diagnostics are zero-tolerance. Directory.Build.props enables warnings-as-errors for Release, so dotnet build -c Release is 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.
  4. 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.
  5. NuGet and both npm dependency graphs must contain zero known advisories of any severity. Container scanning is nightly and separately reported.
  6. PR validation covers develop and master; a non-publishing build runs after every merge to master; package publication and release documentation deployment remain tag-only.
  7. 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.
  8. 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.
  9. The .NET SDK feature band is pinned by global.json: version names the lowest accepted patch and rollForward: latestPatch accepts 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.

Updated at:

Released under the MIT License.