Skip to content

Quality gates

What CI enforces, per workflow. CI is GitHub Actions: one workflow per module in .github/workflows/, each filtered by path, so a pull request runs the gates of the modules it changes. A failed gate blocks merge (branching).

infra/azure-devops/ holds the Azure DevOps equivalent of each deployable's pipeline, with the same stages and path filters. They are samples and are not wired to a project.

Common to every module

Gate Threshold
Build zero errors; .NET builds treat warnings as errors (TreatWarningsAsErrors, nullable reference types, AnalysisLevel latest-recommended, SonarAnalyzer.CSharp)
Lint and format zero findings
Tests all pass
Architecture rules all pass (NetArchTest, import-linter, eslint-plugin-boundaries)
Coverage floor per module, below; enforced by the build, not by review

Per workflow

Workflow Runs when Build Lint and format Tests and coverage Architecture Other
api.yml services/api/**, contracts/** (PR), the workflow, deploy-container-app.yml dotnet build -c Release with the .NET and SonarAnalyzer.CSharp analyzers dotnet format --verify-no-changes 204 tests incl. Testcontainers SQL Server; ReportGenerator merged line coverage ≥ 80% 23 architecture tests image build and push on main
functions.yml services/functions/**, contracts/** (PR), the workflow dotnet build -c Release with the .NET and SonarAnalyzer.CSharp analyzers dotnet format --verify-no-changes 186 tests; coverlet.msbuild line and branch ≥ 80% NetArchTest and reflection tests zip package on main
insights.yml services/insights/**, contracts/** (PR), the workflow, deploy-container-app.yml uv sync --frozen ruff check (rule families selected in pyproject.toml, including bugbear, simplify, comprehensions, Pylint, Bandit, Perflint and Ruff), ruff format --check 178 tests; coverage ≥ 85% with branches; response snapshots lint-imports (7 contracts) mypy --strict on src and tests; image build on every run
web.yml apps/web/**, contracts/** (PR), the workflow pnpm build (tsc -b + Vite) ESLint (typescript-eslint strict type-checked, react-hooks, eslint-plugin-sonarjs recommended), Prettier check 183 tests; Vitest thresholds 70% global, 95% lines on features/*/domain, 90% on shared/{format,config,http} eslint-plugin-boundaries tsc --noEmit typecheck; pnpm install --frozen-lockfile; image build on every run
infra.yml infra/**, the workflow az bicep build, az bicep build-params for both power states az bicep lint with every rule at error in bicepconfig.json; ShellCheck on infra/scripts Logic App JSON check; Azure DevOps YAML parses; az deployment group validate and what-if with OIDC, written to the job summary
docs.yml docs/**, the workflow mkdocs build --strict (broken links and anchors fail)
platform.yml docker-compose.yml, .env.example, local/**, scripts/**, **/*.md, **/*.yml, **/*.yaml, **/Dockerfile, the lint configs, .github/** docker compose config (default and functions profile) markdownlint-cli2 (.markdownlint-cli2.jsonc), yamllint (.yamllint.yaml), hadolint on every Dockerfile (.hadolint.yaml), Markdown link check over every .md file, ShellCheck on scripts/ and the web container entrypoint, actionlint promtool test rules promtool check config, amtool check-config, Service Bus emulator JSON

After merge

Deploy jobs run only on pushes to main and target the GitHub environment production.

Stage Gate
Image publish (API, Insights, Web) pushed to ghcr.io/marcelo-roman/incident-ops-api, -insights, -web, tagged with the commit SHA and latest
Production approval production environment required reviewer + release readiness
Azure login OIDC federated credential for repo:marcelo-roman@195764956/incident-ops@1401969545:environment:production; no stored client secret
Container Apps local reusable ./.github/workflows/deploy-container-app.yml: new revision, readiness wait, smoke test on the health URL, rollback by copying the previous revision; the API skips the readiness wait and smoke test while the environment is powered off
Functions, Web Azure/functions-action@v1 to func-incident-ops; Azure/static-web-apps-deploy@v1
Infra what-if in the job summary before the approval; deploy on main
Post-deploy revision promotion criteria per SLO

Not automated yet

These are review-time expectations today, not CI gates:

  • secret scanning;
  • dependency vulnerability audit and container image scanning;
  • non-root user and health check in every Dockerfile (hadolint checks syntax and practices, review checks these two);
  • OpenAPI document diff and bundle size budget;
  • Playwright smoke test of the console (pnpm test:e2e, run on demand);
  • a Functions host start against the Service Bus emulator.

Linter configuration

The repository-level linters read their configuration from the repository root, so the editor and CI report the same findings. .vscode/extensions.json recommends the matching VS Code extensions.

Linter Configuration Scope
markdownlint-cli2 .markdownlint-cli2.jsonc; apps/web/.markdownlint-cli2.jsonc sets aligned tables, the style Prettier writes there every *.md except generated output, report templates and test snapshots
yamllint .yamllint.yaml every *.yml and *.yaml except pnpm-lock.yaml
hadolint .hadolint.yaml every tracked Dockerfile
SonarAnalyzer.CSharp GlobalPackageReference in each solution's Directory.Packages.props services/api, services/functions
eslint-plugin-sonarjs apps/web/eslint.config.js apps/web
Ruff services/insights/pyproject.toml services/insights

GitHub Actions and Azure DevOps equivalents

Concern GitHub Actions (live) Azure DevOps (infra/azure-devops/ sample)
Path filtering on.pull_request.paths, on.push.paths trigger.paths.include, pr.paths.include
PR validation on: pull_request + review of the checks that ran pr: trigger + build validation branch policy per path
Azure auth OIDC via azure/login@v2 workload identity federation service connection sc-incident-ops
Approval environment production, required reviewers environment incident-ops-prod, approvals and branch control
Reuse local reusable workflow deploy-container-app.yml step template templates/deploy-container-app.yml
Secrets repository and environment secrets variable group vg-incident-ops
Run summary job summary (what-if, coverage) published test results, pipeline artifacts, summary tab

Changing a gate

Lowering a threshold needs an ADR-light: a PR to this file and to the module's configuration with the reason and an expiry date, approved by the code owner. Raising one is a normal PR.