Rule Reference
Every rubric criterion this platform checks: what it checks, why, and (where a static evaluator exists) exactly how, per language.
No rules match these filters.
NFRGate catches missing logs, metrics, traces, and reliability gaps — static analysis plus LLM judgment, gating CI/CD before they ever reach production.
$pip install nfrgateThe same loop SonarQube popularized for code quality — applied to non-functional requirements, with every confidence number measured, not guessed.
The CI job reads only the diff — changed lines in Python, Java, Go, and Rust (preview) files.
Static rules for logs, metrics, traces, and reliability, plus an optional LLM review for what no static rule can see.
By measured confidence and the service's criticality — a merge is only blocked at 0.85+ confidence.
Ordinary code with common observability gaps — and exactly what NFRGate reports on it.
payments/client.py1import logging2import requests34logger = logging.getLogger(__name__)567def charge(order):8 try:9 resp = requests.post("https://api.example.com/charge", json=order)10 return resp.json()11 except Exception:12 passEmpty or pass-through handler (bare `pass` or a no-op re-raise) — the exception is swallowed with no log entry.
No logging call found in this except block.
src/main/java/com/example/PaymentClient.java1package com.example;23public class PaymentClient {4 public void charge(Order order) {5 try {6 gateway.submit(order);7 } catch (Exception e) {8 }9 }10}Empty catch block or a bare re-throw with nothing else — the exception is swallowed with no log entry.
No logging call found in this catch block.
payments/client.go1package payments23import "net/http"45func Charge(url string) error {6 resp, err := http.Get(url)7 if err != nil {8 return nil9 }10 defer resp.Body.Close()11 return nil12}No context.WithTimeout()/WithDeadline() call found earlier in the enclosing function.
No tracer.Start(...) call found earlier in the enclosing function.
No propagator .Inject(...) call found in the enclosing function — can't confirm trace context is propagated from syntax alone (a wrapped transport that injects automatically wouldn't show this either).
Returns without including the error and without logging it — the failure is discarded; the caller has no way to know it happened.
No logging call found in this error-check block — note error wrapping/propagation without logging can be intentional in Go; confirm the caller logs it.
src/payments.rs1use reqwest::Client;23pub async fn charge(client: &Client, order: &Order) -> Option<Receipt> {4 match client.post(CHARGE_URL).json(order).send().await {5 Ok(resp) => resp.json().await.ok(),6 Err(_) => None,7 }8}No timeout on this call, and the client is constructed outside this file — confirm it's built with .timeout(..).
No #[instrument] on the enclosing fn and no span created before this call.
No trace-context propagation (TracingMiddleware, HeaderInjector, inject_context) found in this file — it may be configured where the client is built.
`Err(_)` ignores the error and substitutes a value with no log entry — the failure becomes invisible.
Not a mock-up: these findings were produced by running NFRGate's own analyzer on this code when this page was built, and routed as they would be for a tier1 service. The tick on each confidence bar marks the 0.85 blocking threshold.
One comment per PR/MR, updated on every push — not a wall of separate bot comments.
NFR Gate — Static Analysis
2 fail (2 blocking) · 6 pass · 1 unclear across 4 changed files.
R1payments/client.py:88outbound call has no timeout configuredT1payments/client.py:88call not wrapped in a trace spanL2payments/client.py:102structured error log presentComplete, runnable job definitions — not just the invocation line. Both read the diff, post the comment, and fail the job on a blocking violation.
name: NFRGate
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write # needed to post/update the PR comment
jobs:
nfrgate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history -- pr_gate.py diffs base...head, a shallow clone can't resolve an arbitrary base SHA
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install nfrgate
- name: Run NFRGate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python -m nfr_gate.ci.pr_gatenfrgate:
stage: test
image: python:3.12-slim
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
variables:
GIT_DEPTH: 0 # full history, same reason as the GitHub Actions example
script:
- pip install nfrgate
- python -m nfr_gate.ci.mr_gateStatic rules implemented per language and category — computed from the rule registry itself.
| Language | 📝 Logs | 📊 Metrics | 🛡️ Reliability | 🔍 Traces |
|---|---|---|---|---|
| 4/4 | 3/4 | 3/4 | 4/4 | |
| 4/4 | 3/4 | 3/4 | 4/4 | |
| 4/4 | 3/4 | 3/4 | 4/4 | |
| 4/4 | 3/4 | 3/4 | 4/4 |
M4, R4 are judged by the LLM review from the PR/MR text, in every language.
Every rubric criterion this platform checks: what it checks, why, and (where a static evaluator exists) exactly how, per language.
No rules match these filters.
16 rules across logs, metrics, traces, and reliability — things like swallowed exceptions, missing timeouts, and external calls without trace spans. Every rule has its own page in the rule reference.
Static analysis covers Python, Java, and Go, plus Rust in preview: Rust findings aren't calibrated yet, so every one is routed to a human reviewer rather than blocking. A few judgment-call criteria are assessed by the optional LLM review from the PR/MR text, regardless of language.
Only when a check fails with at least 0.85 measured confidence, on a medium, high, critical-severity rule, for a service that isn't tier0. Everything else is posted as a finding for human review — nothing is silently dropped. A tier0 (customer-critical) service always goes to a human.
No. Static analysis, routing, and blocking work out of the box. The LLM review is optional: it defaults to a local Ollama model, or you can set NFR_GATE_LLM_BACKEND=anthropic with an ANTHROPIC_API_KEY.
Static analysis runs entirely inside your own CI job — no code leaves it.
The LLM review defaults to a local Ollama model, so nothing leaves your infrastructure either. Only if you set NFR_GATE_LLM_BACKEND=anthropic are the diff and the PR/MR title and description sent to Anthropic's API for assessment.
Findings go only where you point them: the comment on your own PR/MR, your own findings database (NFR_GATE_DATABASE_URL), and Slack or Jira if you configure those.
Yes — the job definitions in the guide run on standard hosted runners. A hosted runner can't reach a local Ollama model, so for the LLM review there use the Anthropic backend or a self-hosted runner.
pip install nfrgate, then add the GitHub Actions or GitLab CI job from the user guide — or click Setup help at the bottom right for a step-by-step walkthrough.