shoc-backend/QUALITY_GATES.md
Alexandre Brandizzi 833fb816ee
chore(governance): enforce backend architecture and quality gates (#31)
* docs(governance): add canonical governance docs, unified quality-gate script, and CI parity

- ARCHITECTURE_AND_CODE_QUALITY.md: canonical layering, EF allowlist,
  transaction/commit convention, cancellation, migrations, error disclosure,
  Big-O/perf, ADR exceptions (supersedes BACKEND_ARCHITECTURE.md)
- QUALITY_GATES.md: gate inventory + pass/fail/skip semantics; local==CI
- REVIEW_AND_PR_FRAMEWORK.md: exact-head review, board-backed regression
  inventory, security/perf evidence, ADR exceptions, no godfile theater
- AGENTS.md: repo-specific delta + precedence pointers
- scripts/governance-check.sh: unified G1 restore + G2 ArchitectureTests +
  G3 changed-file format (portable bash)
- .github/workflows/architecture-quality.yml: call the same local script
- ArchitectureTests.cs: add business-service interface-dependency invariant

* fix(governance): make backend gate complete

* fix(ci): enforce governance on every pull request
2026-07-24 17:41:10 -03:00

125 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Quality Gates (canonical)
> **Status: canonical.** This document owns *how* the architecture and
> code-quality rules are enforced: the gate list, the local command, the CI
> mapping, and the pass/fail/skip reporting semantics. The *meaning* of each
> rule lives in `ARCHITECTURE_AND_CODE_QUALITY.md`.
>
> **CI and local run the same check.** Every gate below is executed by
> `scripts/governance-check.sh`. The `architecture-quality` workflow calls that
> script so a green run means the same thing locally and in CI. Do not add a
> check to CI that is not also runnable locally through the script (and vice
> versa).
## Gate inventory
| # | Gate | Protects (architecture §) | Local command | CI step |
|---|------|---------------------------|---------------|---------|
| G1 | Restore | build integrity | `dotnet restore SeaHavenIndustries.sln` | `architecture-quality` → `governance-check.sh` |
| G2 | Architecture boundary tests | §1, §3 (dependency direction) | `dotnet test Api.SeaHavenIndustries.Tests ... --filter FullyQualifiedName~ArchitectureTests --no-restore` | same script |
| G3 | Changed-file formatting | §1 (conventions) | `dotnet format SeaHavenIndustries.sln --no-restore --verify-no-changes --include <changed .cs>` | same script |
| G4 | Release build | compile correctness | `dotnet build SeaHavenIndustries.sln -c Release --no-restore` | `architecture-quality` and `ci` |
| G5 | Full test suite | behavior | `dotnet test SeaHavenIndustries.sln -c Release --no-build` | `architecture-quality` and `ci` |
| G6 | Migration gates | §7 | see §"Migration gates" below | on-demand / release |
| G7 | Cancellation forwarding | §6 | behavior tests on changed I/O paths + analyzer | review-enforced on changed paths |
| G8 | Error disclosure | §5 | `SanitizedErrorsTests` (part of G5) | `ci` |
| G9 | Board-backed regression | review framework | `REVIEW_AND_PR_FRAMEWORK.md` inventory | review-enforced |
## How to run locally
```bash
# Complete local repository gate (mirrors the architecture-quality workflow):
bash scripts/governance-check.sh
# By default it compares against the default branch for changed-file formatting.
# Override the comparison point:
BASE_REF=origin/dev bash scripts/governance-check.sh
BASE_REF=main bash scripts/governance-check.sh
```
The script:
1. `dotnet restore` (G1)
2. runs the `ArchitectureTests` filter with `--no-restore` (G2)
3. computes changed `.cs` files vs `BASE_REF` (default `origin/dev`) and runs
`dotnet format --verify-no-changes --include ...` (G3). When there are no
changed C# files it skips G3 with an explicit "skipped: no changed C#" line.
4. builds the complete solution in Release with no restore (G4).
5. runs the complete solution test suite in Release with no rebuild (G5).
## Migration gates (G6)
Provider-real, not reflection-derived. Run against the configured provider:
- **Actual-provider discovery** — confirm the runtime provider matches the
configured connection (e.g., SQL Server / Npgsql) from configuration, not
from package guesses.
- **Snapshot consistency** — `dotnet ef migrations script --idempotent` (or a
model diff) shows no phantom operations; `ModelSnapshot` matches the model.
- **Empty-db apply** — `dotnet ef database update` against a fresh empty
database applies the full history cleanly.
- **Supported-prior upgrade** — upgrade from each still-supported prior
migration applies cleanly.
- **Rollback** — report **not guaranteed** unless a real downgrade was
exercised against the provider. Never claim "EF can roll it back" untested.
Migration gates are run on-demand and gated for releases; they are not in the
per-PR fast path because they require a database. A PR that adds/changes a
migration must document the G6 evidence in the PR description.
## Cancellation forwarding (G7)
For changed async I/O paths, the requirement (§6) is **verified forwarding**,
not a signature checkbox. Acceptable evidence, any one of:
- a behavior test asserting the `CancellationToken` reaches the data service /
EF call; or
- an analyzer diagnostic confirming propagation on the changed method.
A parameter that exists but is swallowed/`default`-ed is a **fail**. If an
analyzer is unavailable for a path, a behavior test is required; silence is not
acceptable. Review enforces this on changed paths.
## Architecture discovery must fail on zero (G2 invariants)
Each reflection invariant in `ArchitectureTests` asserts the discovered set is
non-empty. A future namespace move that makes reflection return zero types will
**fail the gate** rather than silently pass. The current invariants:
1. Controllers inject neither `DbContext`, concrete data services, nor concrete
service implementations.
2. Business services inject neither `DbContext` nor `IConfiguration`.
3. Business services depend on interfaces (not concrete service/data-service
implementations) for their data and persistence dependencies.
## What this does not claim
- Reflection over constructors proves **wiring shape**, not runtime behavior.
Runtime/semantic properties (cancellation actually cancelling, a query
executing in SQL, a transaction actually committing atomically) are proven by
behavior tests or provider evidence, never by reflection.
- A passing script proves G1–G5 ran in sequence. It does not prove the
provider-backed or review-owned gates G6–G9.
## Pass / fail / skip / not-run reporting
Every gate result is reported as exactly one of:
- **pass** — ran and succeeded, with evidence.
- **fail** — ran and failed; cite the failing test/command and output.
- **skipped** — intentionally not run for this change (e.g., no changed C# for
G3); name the gate and the reason.
- **not-run** — could not run (environment/dependency missing); name the
blocker.
A PR is not "green" if any applicable gate is fail, skipped-without-justification,
or not-run. Never infer a pass from silence.
## Adding or changing a gate
- Any new executable check goes into `scripts/governance-check.sh` **and** the
`architecture-quality` workflow together (or into the full CI for G4/G5-type
checks). One source of truth for local and CI.
- A new rule documents its meaning in `ARCHITECTURE_AND_CODE_QUALITY.md` and its
command/mapping here.
- Explicit, documented exceptions only — no wildcard suppressions (see
`ARCHITECTURE_AND_CODE_QUALITY.md` §10).