Skip to content

FLX-ENG-RFC-005 — Objective CI Gate Configuration · 10 Automated Quality Gates

Field Value
RFC ID FLX-ENG-RFC-005
Status Active — Weeks 1–2 (configuration); Week 3 (enforcement)
Author Arun Singh, Senior Distinguished Engineer / Architect (Consulting)
Reviewers Rahul (Eng Lead), Tushar, Shrikant
Scope 10 automated CI quality gates for DMS — build, coverage, lint, complexity, SAST, CVE, secrets, duplication, dead code, size
Parent Epic GitHub Issue #6 — [EPIC] Objective Code Review CI Configuration
Priority P0-CRITICAL
Related Issues #20 (build), #21 (coverage), #22 (lint), #23 (complexity), #24 (SAST), #25 (CVE), #26 (secrets), #27 (duplication), #28 (dead code), #29 (size)
Blocks RFC-PoC-1.3 (#56) — all gates run inside GitHub Actions build.yml

TL;DR

10 automated quality gates run on every PR to main. Each gate has a pass/fail threshold calibrated to the current DMS codebase baseline. Gates start informational (Week 2) and become merge-blocking (Week 3). This RFC defines what each gate checks, the tool used, the initial threshold, and the step-by-step configuration.

Pre-condition: .NET 8 port (RFC-PoC-1.1 #53) must complete before gates can run reliably on CI.


1. Gate Architecture

PR opened on feature/* → GitHub Actions trigger
    [build] ──────────────────── dotnet build -c Release
         ▼ (on build success)
    [test+coverage] ──────────── dotnet test + reportgenerator
         ├──[lint] ─────────────── dotnet format --verify-no-changes
         ├──[complexity] ────────── dotnet-metrics or NDepend
         ├──[sast] ─────────────── semgrep p/csharp
         ├──[cve-scan] ─────────── trivy fs
         ├──[secrets] ──────────── gitleaks
         ├──[duplication] ───────── jscpd
         ├──[dead-code] ─────────── roslyn unused symbol analysis
         └──[size-gate] ─────────── custom PR diff size check
    All required gates green? → PR can be merged

2. Gate Definitions & Configuration

Gate 1 · Build (GitHub Issue #20)

Priority: P0 | Tool: dotnet build | Phase: Blocking from Day 1

Configuration:

# .github/workflows/build.yml
- name: Build
  run: dotnet build src/distribution-management-server-layered/ -c Release --no-restore

Pass condition: Exit code 0, zero errors
Warning condition: Compiler warnings (CS0xxx) — log but don't fail in Week 1
Fail condition: Any build error (CS8xxx, CS0246, etc.)

Step-by-step setup: 1. Create .github/workflows/build.yml (see RFC-PoC-1.3 #56 for full workflow) 2. Add dotnet restore step before build (cache NuGet packages) 3. Run dotnet build -c Release — capture exit code 4. Upload build log as artifact on failure 5. Calibrate: run locally first, ensure baseline build passes before enabling CI gate


Gate 2 · Test Coverage (GitHub Issue #21)

Priority: P0 | Tool: dotnet test + reportgenerator | Phase: Informational Week 1, Blocking Week 3

Configuration:

- name: Test + Coverage
  run: |
    dotnet test DistributionServerUnitTest/ \
      --collect:"XPlat Code Coverage" \
      --results-directory ./coverage \
      --no-build -c Release

    dotnet tool run reportgenerator \
      -reports:"coverage/**/*.xml" \
      -targetdir:"coverage/report" \
      -reporttypes:"Html;Cobertura"

- name: Coverage Gate
  run: |
    COVERAGE=$(python3 scripts/parse-coverage.py coverage/report/Cobertura.xml)
    echo "Coverage: $COVERAGE%"
    if [ "$COVERAGE" -lt "$MIN_COVERAGE" ]; then
      echo "FAIL: Coverage $COVERAGE% < minimum $MIN_COVERAGE%"
      exit 1
    fi
  env:
    MIN_COVERAGE: 60  # calibrated from baseline in Week 2 pair session

Step-by-step setup: 1. Run dotnet test locally with coverage: dotnet test --collect:"XPlat Code Coverage" 2. Check current coverage percentage (Week 1 baseline scan) 3. Set MIN_COVERAGE = (current actual% - 5%) rounded down to nearest 5 4. In Week 3: raise threshold by 5 percentage points 5. Create scripts/parse-coverage.py to extract line coverage from Cobertura XML 6. Add reportgenerator as dotnet tool: dotnet tool install --global dotnet-reportgenerator-globaltool 7. Exclude generated files from coverage:

# .runsettings
<DataCollectionRunSettings>
  <DataCollectors>
    <DataCollector uri="datacollector://Microsoft/CodeCoverage/2.0">
      <Configuration>
        <Exclude>
          <ModulePath>.*Migrations.*</ModulePath>
          <ModulePath>.*Program.*</ModulePath>
        </Exclude>
      </Configuration>
    </DataCollector>
  </DataCollectors>
</DataCollectionRunSettings>


Gate 3 · Linter / Format (GitHub Issue #22)

Priority: P1 | Tool: dotnet format | Phase: Blocking from Week 2

Note: Issue #22 mentions Pylint/ruff which are Python linters. DMS is C#. The correct tool is dotnet format for C# formatting enforcement. Issue #22 should be updated to reflect dotnet format not Pylint.

Configuration:

- name: Format Check
  run: dotnet format src/distribution-management-server-layered/ --verify-no-changes --verbosity diagnostic

Pass condition: No formatting changes needed (exit 0)
Fail condition: Any unformatted file detected (exit non-zero)

Step-by-step setup: 1. Ensure .editorconfig exists at repo root with C# formatting rules:

[*.cs]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
2. Run dotnet format src/ --verify-no-changes locally first 3. If it fails: run dotnet format src/ (without --verify) to fix all files in one commit 4. Then add to CI as blocking gate 5. Add pre-commit hook (optional, Week 3): dotnet format on staged C# files


Gate 4 · Cyclomatic Complexity (GitHub Issue #23)

Priority: P2 | Tool: dotnet-metrics or Roslyn analyzer | Phase: Informational Week 2, Blocking Week 3

Configuration:

- name: Complexity Analysis
  run: |
    dotnet build src/ -c Release -p:EnableRoslynAnalyzers=true
    # CA1502: AvoidExcessiveComplexity is a built-in Roslyn rule
    # Add to .editorconfig:
    # dotnet_diagnostic.CA1502.severity = error

OR use lizard for cross-language complexity:

- name: Complexity (lizard)
  run: |
    pip install lizard
    lizard src/ -l cs --CCN 10 --length 50 -w

Pass condition: No method with cyclomatic complexity > 10
Warning: Complexity 8–10
Fail: Complexity > 10 (any single method)

Step-by-step setup: 1. Run lizard src/ -l cs locally — identify current max complexity 2. List the top 10 most complex methods (likely in service layer or sorting logic) 3. Set initial threshold to max(current highest, 15) — don't fail everything on Day 1 4. In Week 3: lower threshold to 10 (per ITIL best practice for maintainability) 5. For existing violations: create separate refactoring issues, don't block this gate


Gate 5 · SAST — Static Application Security Testing (GitHub Issue #24)

Priority: P0 | Tool: Semgrep (C# ruleset) | Phase: Blocking from Day 1 (Critical), Informational (High)

Configuration:

- name: SAST (Semgrep)
  uses: semgrep/semgrep-action@v1
  with:
    config: >-
      p/csharp
      p/owasp-top-ten
      p/secrets
    generateSarif: true
  env:
    SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}  # optional for CI

- name: Upload SAST SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: semgrep.sarif

Pass condition: Zero Critical severity findings
Fail condition: Any Critical finding; >3 High findings
Warning condition: High findings (1–3) — logged, not blocking in Week 1

Step-by-step setup: 1. Run Semgrep locally: semgrep --config "p/csharp" src/ --json > semgrep-results.json 2. Review findings: cat semgrep-results.json | jq '.results[] | {severity:.extra.severity, rule:.check_id, path:.path}' 3. For each High/Critical: decide suppress or fix 4. Create .semgrepignore for false positives (document each with reason) 5. Add semgrep.sarif upload to GitHub Code Scanning (visible in Security tab) 6. Set Semgrep to fail CI on Critical: semgrep --error --severity=error

Common DMS findings to expect: - SQL injection via string interpolation in raw queries - Missing input validation on API parameters - Hardcoded credentials in test files (false positive if using test constants)


Gate 6 · Dependency CVE Scan (GitHub Issue #25)

Priority: P0 | Tool: Trivy | Phase: Blocking (Critical CVEs) from Day 1

Configuration:

- name: CVE Scan (Trivy)
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    exit-code: '1'
    severity: 'CRITICAL'
    format: 'sarif'
    output: 'trivy.sarif'

- name: Upload CVE SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy.sarif

Pass condition: Zero CRITICAL CVEs
Fail condition: Any CRITICAL CVE
Warning condition: HIGH CVEs ≥5 fails; <5 is warning only

Step-by-step setup: 1. Install Trivy: brew install trivy 2. Run locally: trivy fs . --severity CRITICAL,HIGH 3. Review findings: note package name, CVE ID, current version, fixed version 4. For each CRITICAL: update package to fixed version immediately 5. For HIGH: document in docs/security/cve-exceptions.md with: - CVE ID - Package - Why it's acceptable for now - Planned fix date 6. Configure trivy.yaml for suppressed findings:

vulnerabilities:
  ignore-unfixed: true
  severity:
    - CRITICAL


Gate 7 · Secret Scan (GitHub Issue #26)

Priority: P0 | Tool: gitleaks | Phase: Blocking from Day 1

Configuration:

- name: Secret Scan (gitleaks)
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    GITLEAKS_ENABLE_COMMENTS: false

Pass condition: Zero secrets detected
Fail condition: Any secret detected (API key, password, token, private key)
No exceptions: This gate is never suppressed for secrets. If a false positive is found: add to .gitleaks.toml with documented justification.

Step-by-step setup: 1. Create .gitleaks.toml at repo root:

title = "Flexli DMS Gitleaks Configuration"

[allowlist]
  description = "Allowlisted false positives"
  paths = [
    '''DistributionServerUnitTest/.*TestConstants.*\.cs'''
  ]
  regexes = [
    '''EXAMPLE_KEY''',  # test fixture constants
  ]
2. Run locally on full history: gitleaks detect --source . --log-opts="HEAD~100..HEAD" 3. If any real secrets found in history: rotate the credential FIRST, then clean git history 4. Confirm appsettings.Development.json, appsettings.Production.json are gitignored (they are per existing .gitignore) 5. Add to pre-commit hook: gitleaks protect --staged


Gate 8 · Code Duplication (GitHub Issue #27)

Priority: P2 | Tool: jscpd | Phase: Informational Week 2, Blocking Week 3

Configuration:

- name: Duplication Check (jscpd)
  run: |
    npx jscpd src/ --min-lines 10 --min-tokens 50 \
      --languages csharp \
      --threshold 5 \
      --reporters json \
      --output /tmp/jscpd-report

    DUPE_PERCENT=$(cat /tmp/jscpd-report/jscpd-report.json | jq '.statistics.total.percentage')
    echo "Duplication: $DUPE_PERCENT%"
    if (( $(echo "$DUPE_PERCENT > 5" | bc -l) )); then
      echo "FAIL: Duplication $DUPE_PERCENT% > 5% threshold"
      exit 1
    fi

Pass condition: < 5% code duplication
Fail condition: ≥ 5% duplication

Step-by-step setup: 1. Run npx jscpd src/ --languages csharp locally 2. Note current duplication percentage (likely higher than 5% in Week 1) 3. Set initial threshold to (current + 2%) — don't fail everything on Day 1 4. In Week 3: lower to 5% after addressing most obvious duplications 5. Review jscpd report: identify top duplicated blocks → candidates for extraction to shared utilities


Gate 9 · Dead Code Detection (GitHub Issue #28)

Priority: P2 | Tool: Roslyn unused symbol analysis | Phase: Informational only

Configuration:

- name: Dead Code Analysis
  run: |
    dotnet build src/ -c Release \
      -p:TreatWarningsAsErrors=false \
      -p:EnableRoslynAnalyzers=true \
      2>&1 | grep -E "CS0649|CS0169|CS8618|IDE0051|IDE0052" \
      | tee /tmp/dead-code.txt

    DEAD_COUNT=$(wc -l < /tmp/dead-code.txt)
    echo "Dead code indicators: $DEAD_COUNT"
    # Week 1-2: informational only
    # Week 3: fail if > 10 indicators

Relevant Roslyn rules: - CS0649: Field is never assigned - CS0169: Field is never used - IDE0051: Remove unused private members - IDE0052: Remove unread private members

Step-by-step setup: 1. Add to .editorconfig:

dotnet_diagnostic.IDE0051.severity = warning
dotnet_diagnostic.IDE0052.severity = warning
2. Run dotnet build and count warning output for these rules 3. Document current dead code count as baseline 4. Create separate cleanup issues for any dead code found in critical paths


Gate 10 · File and Function Size (GitHub Issue #29)

Priority: P2 | Tool: Custom Python script | Phase: Informational Week 2, Blocking Week 3

Configuration:

- name: Size Gate
  run: python3 scripts/size-gate.py src/ --max-file-lines 400 --max-method-lines 50

# scripts/size-gate.py
import os, re, sys, argparse

def check_sizes(src_dir, max_file_lines, max_method_lines):
    violations = []
    for root, _, files in os.walk(src_dir):
        for f in files:
            if not f.endswith('.cs'):
                continue
            path = os.path.join(root, f)
            lines = open(path).readlines()
            if len(lines) > max_file_lines:
                violations.append(f"FILE TOO LARGE: {path} ({len(lines)} lines > {max_file_lines})")
    return violations

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('src_dir')
    parser.add_argument('--max-file-lines', type=int, default=400)
    parser.add_argument('--max-method-lines', type=int, default=50)
    args = parser.parse_args()

    violations = check_sizes(args.src_dir, args.max_file_lines, args.max_method_lines)
    for v in violations:
        print(v)
    sys.exit(1 if violations else 0)

Thresholds: - File: ≤ 400 lines (week 1: set to actual max + 50% as informational threshold) - Method: ≤ 50 lines


3. Gate Rollout Timeline

Gate Tool Week 1 Week 2 Week 3
Build dotnet build Blocking Blocking Blocking
Coverage dotnet test Informational Informational Blocking
Lint dotnet format Blocking Blocking Blocking
Complexity lizard/roslyn Off Informational Blocking
SAST semgrep Blocking (Critical) Blocking (Critical) Blocking (High+)
CVE trivy Blocking (Critical) Blocking (Critical) Blocking (Critical)
Secrets gitleaks Blocking Blocking Blocking
Duplication jscpd Off Informational Blocking
Dead Code roslyn Off Informational Informational
Size custom Off Informational Informational

4. Dependencies

Dependency Required for
.NET 8 port (RFC-PoC-1.1 #53) All gates — cannot run reliably on unsupported .NET 6 SDK
GitHub Actions CI (RFC-PoC-1.3 #56) Gate execution engine
Week 2 pair session (US-2.2 #18) Threshold calibration for coverage and complexity

5. Success Criteria

  • All 10 gates defined in build.yml with correct tools and thresholds
  • Gates 1, 5, 6, 7 blocking from Week 1
  • All 10 gates blocking by Week 3
  • First-pass scan results committed to docs/developer-guide/week1-scans/
  • No suppressions without documented justification in docs/security/