Nothing merges red, and gates run in CI, not on someone’s machine. When time is short, cut scope, not gates.
A gate is only real if a merge cannot bypass it. If two teams both say “lint is clean,” they must run the same check, in the same place, with the same power to block. This page names the check for each surface and the mechanism that enforces it.
Blocking gates
Every gate below is required before merge. Some values are still being set across the org, such as the patch-coverage threshold and the standard SAST engine. The mechanism is required now, and the value is tracked in open questions.
- Typecheck or compile with zero new errors; lint and format clean against the repository baseline.
- Update tests with the code they cover: unit tests for new functionality, plus integration or end-to-end tests when the change needs them. See testing for what each layer requires.
- Changed lines must meet the org patch-coverage threshold. Use diff coverage, not whole-repo coverage, so new code is covered without changing legacy modules.
- SAST must be clean, and dependency advisories must be clean or deferred through exceptions. See security for the scanners.
- Secret scanning must be clean. Push protection is on, so a pushed credential blocks the push before it reaches history.
- A production build or deployable artifact must succeed. Deploy the CI-built artifact, not a local build.
- Independent approval, per four-eyes.
- For a user-facing change, check the real rendered or returned result, not only a green run.
flowchart LR
PR[Pull request] --> G{All gates green?}
G -- no --> Blocked[Merge blocked]
G -- yes --> Rev{Independent approval?}
Rev -- no --> Blocked
Rev -- yes --> Merge[Merge]
The check per surface
“Lint and format clean” is clear only when the tool is named. The gate runs the tool for the surface it changes:
| Surface | Format / lint | Types |
|---|---|---|
| Scala / JVM | scalafmt, scalafix, WartRemover |
-Werror compile |
| TypeScript | prettier, eslint |
tsc --noEmit |
| Python | ruff format, ruff check |
mypy |
| Dart / Flutter | dart format |
dart analyze |
| C# / .NET | dotnet format |
Roslyn analyzers |
| Terraform | terraform fmt, tflint |
terraform validate |
The language recipes are in NT-NinjaTrader/testing-handbook. This page names the gate; the handbook shows the config.
Enforcement
Gates are GitHub repository rulesets with required status checks. A red run cannot be bypassed. No bypass actors are configured, including admins, and --no-verify is prohibited. Local pre-commit hooks help, but they are not the source of truth.
# Required status checks, enforced as a repository ruleset.
# strict = re-verify against the latest base (pairs with a merge queue).
required_checks:
- build
- lint-and-format
- typecheck
- test # includes the patch-coverage gate
- codeql
- dependency-reviewA merge queue is preferred. It checks main against the actual post-merge state and keeps it green during concurrent merges.
The SAST and dependency gates run in the pipeline itself:
jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # CodeQL upload
pull-requests: read # dependency review
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# fails on vulnerable dependencies introduced by the PR
- uses: actions/dependency-review-action@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript-typescript,python
- uses: github/codeql-action/analyze@v3CodeQL covers JS/TS, Python, Java, C#, and Go, but not Scala. Scan the Scala backend with Semgrep or an equivalent. Treat “which SAST engine per surface” as a language-by-language choice, not one tool for everything.
For Terraform and Atlantis repositories, add the infrastructure gate before trusting a plan:
terraform fmt -check -recursive
terraform init -backend=false && terraform validate
tflint --init && tflint --recursive
checkov -d . --quiet --compact
# any non-zero exit fails the buildBlocking versus advisory
Say which checks block and which only provide information. A blocking gate stops the merge. An advisory check reports and continues. Do not let an advisory check quietly become required, or let a blocking gate become a rubber stamp.
Supply chain
Anything that can be deployed must pass a supply-chain gate: pin base images and GitHub Actions by digest, produce a software bill of materials for each build with CycloneDX or Syft, and sign artifacts with build provenance (cosign, SLSA). See CI/CD for where these run and security for the policy.
Deferrals
Defer a failing advisory or a dependency you cannot patch yet through the exceptions process. Include a named owner and an expiry, never an informal in-repo comment. A gate that everyone learns to skip is not a gate.
Referenced standards
- Conventional Commits, the PR-title and commit-message check.
- CodeQL, code scanning for supported languages.
- OWASP Dependency-Check, dependency advisories.