Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
19 Oct 2026 · 21 mnt baca ·Artikel 69 / 208
Go

Branch Protection AI Quality Gate Golang: Setup Lengkap untuk Go Projects

Setup branch protection dengan AI quality gate untuk Go project. Required status checks, aggregate scoring, CODEOWNERS, merge queue, dan enforcement strategy yang sustainable.

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

Branch Protection dengan AI Quality Gate

Branch protection AI quality gate golang adalah kunci yang mengubah semua quality gate dari sekadar saran menjadi hard requirement yang tidak bisa di-bypass. Di artikel sebelumnya, kita sudah bangun empat quality gates: AI code review, security audit, coverage enforcement, dan architecture linting. Sekarang pertanyaannya: bagaimana membuat semua ini menjadi mandatory?

Jawabannya adalah GitHub Branch Protection dengan required status checks. Artikel ini adalah penutup rangkaian quality gate yang menggabungkan semua gate ke dalam satu sistem yang koheren.


09.1 Branch Protection: Konsep Dasar

Sebelum menyetel apa pun, kita perlu tahu apa saja yang bisa di-protect di GitHub. Daftar berikut merangkum opsi branch protection dan status check yang sudah kita kumpulkan dari artikel-artikel sebelumnya.

text
 1Branch protection rules di GitHub:
 2  Settings → Branches → Add rule
 3
 4Apa yang bisa di-protect:
 5  ✅ Require pull requests (tidak ada direct push ke main)
 6  ✅ Require status checks (pipeline harus pass)
 7  ✅ Require signed commits
 8  ✅ Require linear history
 9  ✅ Require conversation resolution
10  ✅ Restrict who can push
11
12Yang paling penting untuk kita:
13  "Require status checks to pass before merging"
14  → Ini yang membuat semua pipeline gates menjadi mandatory
15
16Status checks yang kita punya dari Part 1-2:
17  ✅ L1: Go Build, Test & Lint
18  ✅ L2: Architecture Boundary Check
19  ✅ L3: AI Semantic Review (Copilot)
20  ✅ L4: Spec Compliance Audit
21  ✅ Security: Secret Detection
22  ✅ Security: Static Analysis (gosec)
23  ✅ Security: Dependency Vuln (govulncheck)
24  ✅ Coverage: Per-layer thresholds
25  ✅ Architecture: depguard + custom script

Dari semua opsi, “Require status checks to pass before merging” adalah yang paling penting — inilah mekanisme yang mengubah pipeline gate kita menjadi syarat wajib sebelum merge.


09.2 Aggregate Quality Score

Punya sembilan status check yang terpisah bisa membingungkan. Kita butuh satu skor agregat yang menggabungkan semua gate menjadi satu verdict. Program Go berikut menghitung weighted score dan menentukan PASS, WARNING, atau FAIL.

go
  1// cmd/quality-gate/main.go
  2// Aggregate semua quality scores menjadi satu final verdict
  3
  4package main
  5
  6import (
  7    "encoding/json"
  8    "fmt"
  9    "os"
 10)
 11
 12type QualityResult struct {
 13    Gate    string  `json:"gate"`
 14    Score   int     `json:"score"`   // 0-100
 15    Weight  float64 `json:"weight"`  // relative importance
 16    Pass    bool    `json:"pass"`
 17    Details string  `json:"details,omitempty"`
 18}
 19
 20type AggregateResult struct {
 21    Gates        []QualityResult `json:"gates"`
 22    WeightedScore int            `json:"weighted_score"`
 23    AllCritical  bool            `json:"all_critical_pass"`
 24    Verdict      string          `json:"verdict"` // PASS|WARNING|FAIL
 25}
 26
 27func main() {
 28    // Load individual gate results (dari CI artifacts atau env vars)
 29    gates := []QualityResult{
 30        {
 31            Gate:   "code_review",
 32            Score:  getEnvInt("AI_REVIEW_SCORE", 85),
 33            Weight: 0.30, // 30% weight
 34            Pass:   getEnvInt("AI_REVIEW_SCORE", 85) >= 60,
 35        },
 36        {
 37            Gate:   "security",
 38            Score:  getEnvInt("SECURITY_SCORE", 90),
 39            Weight: 0.25, // 25% weight — security is important
 40            Pass:   getEnvInt("SECURITY_SCORE", 90) >= 70,
 41        },
 42        {
 43            Gate:   "test_coverage",
 44            Score:  getEnvInt("COVERAGE_SCORE", 82),
 45            Weight: 0.25, // 25% weight
 46            Pass:   getEnvInt("COVERAGE_SCORE", 82) >= 75,
 47        },
 48        {
 49            Gate:   "architecture",
 50            Score:  getEnvInt("ARCH_SCORE", 95),
 51            Weight: 0.20, // 20% weight
 52            Pass:   getEnvInt("ARCH_SCORE", 95) >= 80,
 53        },
 54    }
 55
 56    // Calculate weighted score
 57    totalWeight := 0.0
 58    weightedSum := 0.0
 59    allCriticalPass := true
 60
 61    for _, g := range gates {
 62        weightedSum += float64(g.Score) * g.Weight
 63        totalWeight += g.Weight
 64        if !g.Pass {
 65            allCriticalPass = false
 66        }
 67    }
 68
 69    weightedScore := int(weightedSum / totalWeight)
 70
 71    // Determine verdict
 72    verdict := "PASS"
 73    if !allCriticalPass {
 74        verdict = "FAIL"
 75    } else if weightedScore < 75 {
 76        verdict = "WARNING"
 77    }
 78
 79    result := AggregateResult{
 80        Gates:         gates,
 81        WeightedScore: weightedScore,
 82        AllCritical:   allCriticalPass,
 83        Verdict:       verdict,
 84    }
 85
 86    // Output
 87    jsonOut, _ := json.MarshalIndent(result, "", "  ")
 88    fmt.Println(string(jsonOut))
 89
 90    // Print summary
 91    fmt.Printf("\n=== Quality Gate Summary ===\n")
 92    for _, g := range gates {
 93        status := "✅"
 94        if !g.Pass {
 95            status = "❌"
 96        }
 97        fmt.Printf("%s %-20s %d/100\n", status, g.Gate, g.Score)
 98    }
 99    fmt.Printf("\nWeighted Score: %d/100\n", weightedScore)
100    fmt.Printf("Verdict: %s\n", verdict)
101
102    if verdict == "FAIL" {
103        os.Exit(1)
104    }
105}
106
107func getEnvInt(key string, defaultVal int) int {
108    val := os.Getenv(key)
109    if val == "" {
110        return defaultVal
111    }
112    var i int
113    fmt.Sscanf(val, "%d", &i)
114    return i
115}

Logika verdict-nya penting: SEMUA gate harus lulus threshold individualnya (allCriticalPass), DAN weighted average harus melampaui 75. Satu gate yang gagal langsung memaksa verdict FAIL, berapa pun tingginya skor rata-rata.


09.3 GitHub Actions: Final Quality Gate Job

Skor agregat perlu dijalankan sebagai job final yang mengumpulkan hasil semua gate lain. Workflow berikut memakai if: always() agar tetap jalan meski beberapa job gagal, lalu membangun dan menjalankan binary quality-gate.

yaml
  1# Tambahkan ke .github/workflows/ci.yml — sebagai final gate
  2
  3jobs:
  4  # ... semua jobs sebelumnya (L1, L2, L3, L4, security, coverage, arch) ...
  5
  6  # FINAL GATE: Aggregate semua scores
  7  quality-gate:
  8    name: "Final Quality Gate"
  9    runs-on: ubuntu-latest
 10    needs:
 11      - go-quality         # L1
 12      - architecture       # L2
 13      - ai-review          # L3
 14      - spec-compliance    # L4
 15      - static-security    # security
 16      - dependency-scan    # security
 17      - coverage-check     # coverage
 18    if: always()  # run bahkan jika beberapa jobs fail
 19    permissions:
 20      pull-requests: write
 21      contents: read
 22    steps:
 23      - uses: actions/checkout@v4
 24      - uses: actions/setup-go@v5
 25        with: { go-version: '1.22' }
 26
 27      - name: Collect gate results
 28        id: collect
 29        run: |
 30          # Collect scores dari setiap gate (via env atau artifacts)
 31          # Untuk simplicity: ambil dari job status
 32
 33          # L1: Go quality
 34          GO_SCORE=$([[ "${{ needs.go-quality.result }}" == "success" ]] && echo 90 || echo 0)
 35
 36          # Security
 37          SEC_SCORE=$([[ "${{ needs.static-security.result }}" == "success" ]] && echo 90 || echo 50)
 38
 39          # Coverage (dari output jobs)
 40          COV_SCORE="${{ needs.coverage-check.outputs.usecase_coverage || '0' }}"
 41
 42          # Architecture
 43          ARCH_SCORE=$([[ "${{ needs.architecture.result }}" == "success" ]] && echo 95 || echo 40)
 44
 45          echo "AI_REVIEW_SCORE=${{ needs.ai-review.outputs.score || '85' }}" >> $GITHUB_ENV
 46          echo "SECURITY_SCORE=$SEC_SCORE" >> $GITHUB_ENV
 47          echo "COVERAGE_SCORE=$COV_SCORE" >> $GITHUB_ENV
 48          echo "ARCH_SCORE=$ARCH_SCORE" >> $GITHUB_ENV
 49
 50      - name: Build quality gate binary
 51        run: go build -o /tmp/quality-gate ./cmd/quality-gate/
 52
 53      - name: Run quality gate
 54        id: quality
 55        run: |
 56          RESULT=$(/tmp/quality-gate)
 57
 58          echo "result<<EOF" >> $GITHUB_OUTPUT
 59          echo "$RESULT" >> $GITHUB_OUTPUT
 60          echo "EOF" >> $GITHUB_OUTPUT
 61
 62          VERDICT=$(echo "$RESULT" | jq -r '.verdict')
 63          SCORE=$(echo "$RESULT" | jq '.weighted_score')
 64
 65          echo "verdict=$VERDICT" >> $GITHUB_OUTPUT
 66          echo "score=$SCORE" >> $GITHUB_OUTPUT
 67
 68      - name: Post final quality summary to PR
 69        if: github.event_name == 'pull_request'
 70        uses: actions/github-script@v7
 71        with:
 72          script: |
 73            const result = JSON.parse(`${{ steps.quality.outputs.result }}`);
 74            const verdict = '${{ steps.quality.outputs.verdict }}';
 75            const score = parseInt('${{ steps.quality.outputs.score }}');
 76
 77            const verdictEmoji = verdict === 'PASS' ? '✅' : verdict === 'WARNING' ? '⚠️' : '❌';
 78            const scoreEmoji = score >= 85 ? '🟢' : score >= 70 ? '🟡' : '🔴';
 79
 80            const gateRows = result.gates.map(g => {
 81              const s = g.pass ? '✅' : '❌';
 82              return `| ${s} | ${g.gate} | ${g.score}/100 | ${Math.round(g.weight*100)}% |`;
 83            });
 84
 85            const body = [
 86              `## 🎯 Final Quality Gate`,
 87              ``,
 88              `**Weighted Score:** ${scoreEmoji} ${score}/100`,
 89              `**Verdict:** ${verdictEmoji} ${verdict}`,
 90              ``,
 91              `| Status | Gate | Score | Weight |`,
 92              `|--------|------|-------|--------|`,
 93              ...gateRows,
 94              ``,
 95              verdict === 'PASS'
 96                ? '✅ **All quality gates passed. Ready to merge!**'
 97                : verdict === 'WARNING'
 98                ? '⚠️ **Some concerns found. Review before merging.**'
 99                : '❌ **Quality gate failed. Fix issues before merging.**',
100            ].join('\n');
101
102            // Find or update final quality comment
103            const { data: comments } = await github.rest.issues.listComments({
104              owner: context.repo.owner,
105              repo: context.repo.repo,
106              issue_number: context.issue.number
107            });
108
109            const existing = comments.find(c => c.body.includes('🎯 Final Quality Gate'));
110
111            if (existing) {
112              await github.rest.issues.updateComment({
113                owner: context.repo.owner,
114                repo: context.repo.repo,
115                comment_id: existing.id,
116                body
117              });
118            } else {
119              await github.rest.issues.createComment({
120                owner: context.repo.owner,
121                repo: context.repo.repo,
122                issue_number: context.issue.number,
123                body
124              });
125            }
126
127      - name: Enforce quality gate
128        run: |
129          VERDICT="${{ steps.quality.outputs.verdict }}"
130          SCORE="${{ steps.quality.outputs.score }}"
131
132          echo "Final Quality Gate: $VERDICT ($SCORE/100)"
133
134          if [ "$VERDICT" == "FAIL" ]; then
135            echo "::error::Quality gate FAILED — fix critical issues before merging"
136            exit 1
137          fi
138
139          if [ "$VERDICT" == "WARNING" ]; then
140            echo "::warning::Quality gate WARNING — review concerns before merging"
141          else
142            echo "✅ Quality gate PASSED"
143          fi

if: always() di sini kritis: tanpa itu, final gate akan di-skip ketika ada job upstream yang gagal, sehingga kita kehilangan ringkasan koheren tentang apa saja yang bermasalah.


09.4 Setup Branch Protection di GitHub

Setelah final gate ada, kita daftarkan sebagai required check di branch protection. Perintah GitHub CLI berikut mengaktifkan required status checks, required reviews, dan linear history untuk branch main.

bash
 1# Via GitHub CLI
 2
 3# Setup branch protection untuk main:
 4gh api repos/{owner}/{repo}/branches/main/protection \
 5  --method PUT \
 6  --field "required_status_checks[strict]=true" \
 7  --field "required_status_checks[contexts][]=L1: Go Build, Test & Lint" \
 8  --field "required_status_checks[contexts][]=L2: Architecture Boundary Check" \
 9  --field "required_status_checks[contexts][]=Final Quality Gate" \
10  --field "required_status_checks[contexts][]=Secret Detection" \
11  --field "required_status_checks[contexts][]=Dependency Vulnerability Scan" \
12  --field "required_pull_request_reviews[required_approving_review_count]=1" \
13  --field "required_pull_request_reviews[dismiss_stale_reviews]=true" \
14  --field "enforce_admins=false" \
15  --field "restrictions=null" \
16  --field "required_linear_history=true" \
17  --field "allow_force_pushes=false" \
18  --field "allow_deletions=false"
19
20# Atau via UI: Settings → Branches → main → Edit

Untuk tim yang menerapkan GitOps, konfigurasi branch protection sebaiknya juga versioned. Workflow berikut menerapkan aturan yang sama lewat GitHub Actions dengan trigger manual.

yaml
 1# Alternatif: branch protection via GitHub Actions (GitOps approach)
 2# .github/workflows/branch-protection.yml
 3
 4name: Setup Branch Protection
 5on:
 6  workflow_dispatch:  # manual trigger
 7
 8jobs:
 9  protect:
10    runs-on: ubuntu-latest
11    permissions:
12      administration: write
13    steps:
14      - uses: actions/github-script@v7
15        with:
16          script: |
17            await github.rest.repos.updateBranchProtection({
18              owner: context.repo.owner,
19              repo: context.repo.repo,
20              branch: 'main',
21              required_status_checks: {
22                strict: true,
23                contexts: [
24                  'L1: Go Build, Test & Lint',
25                  'L2: Architecture Boundary Check',
26                  'Final Quality Gate',
27                  'Secret Detection',
28                ]
29              },
30              required_pull_request_reviews: {
31                required_approving_review_count: 1,
32                dismiss_stale_reviews: true,
33              },
34              enforce_admins: false,
35              restrictions: null,
36              required_linear_history: true,
37              allow_force_pushes: false,
38              allow_deletions: false,
39            });
40            console.log('Branch protection configured');

Pendekatan GitOps ini membuat konfigurasi branch protection ter-version dan bisa di-review, bukan setting tersembunyi di UI yang mudah berubah tanpa jejak.


09.5 CODEOWNERS: Siapa yang Perlu Review Apa

Branch protection bisa mewajibkan review, tapi siapa yang harus me-review file tertentu ditentukan oleh CODEOWNERS. File berikut memetakan path ke tim yang bertanggung jawab, dari arsitektur sampai domain layer.

bash
 1# .github/CODEOWNERS
 2# Format: [path] [owners]
 3
 4# Default: semua perubahan butuh review dari team lead
 5*                   @santekno/engineering-lead
 6
 7# Architecture file: butuh review dari senior architect
 8CLAUDE.md           @santekno/senior-architect
 9.golangci.yml       @santekno/senior-architect
10scripts/check-architecture.go @santekno/senior-architect
11
12# CI/CD pipeline: butuh review dari DevOps
13.github/workflows/  @santekno/devops-team
14
15# Security-related: butuh review dari security team
16.gitleaks.toml      @santekno/security-team
17.gosec.yml          @santekno/security-team
18
19# Domain layer: butuh review dari domain expert
20internal/domain/    @santekno/domain-experts
21
22# Service-specific ownership
23services/order-service/     @johndoe @janedoe
24services/payment-service/   @paymentteam
25services/catalog-service/   @catalogteam

Dengan CODEOWNERS, perubahan pada file arsitektur otomatis meminta review dari architect, perubahan CI dari DevOps, dan perubahan domain dari domain expert — tanpa perlu assign reviewer secara manual setiap kali.


09.6 Merge Queue untuk High-Traffic Repos

Di repo dengan banyak PR paralel, dua PR yang sama-sama lulus CI bisa konflik ketika di-merge bersamaan. Merge queue menyelesaikan ini. Workflow berikut menjalankan semua check plus integration test khusus untuk merge queue.

yaml
 1# Untuk repo dengan banyak concurrent PRs, merge queue mencegah merge conflicts
 2
 3# Enable di GitHub: Settings → General → Merge options → Enable Merge Queue
 4
 5# .github/workflows/merge-queue.yml
 6name: Merge Queue Check
 7
 8on:
 9  merge_group:  # trigger untuk merge queue
10
11jobs:
12  # Semua checks yang sama dengan PR checks
13  # Tapi mungkin lebih strict (e.g., integration tests wajib)
14
15  all-checks:
16    name: All Checks for Merge Queue
17    runs-on: ubuntu-latest
18    steps:
19      - uses: actions/checkout@v4
20      - uses: actions/setup-go@v5
21        with: { go-version: '1.22', cache: true }
22      - run: go build ./...
23      - run: go test -race -count=1 ./...
24      - run: go vet ./...
25      - uses: golangci/golangci-lint-action@v6
26      - run: go run scripts/check-architecture.go ./...
27      # Integration tests di merge queue (bukan di PR)
28      - name: Integration tests
29        env:
30          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
31          REDIS_URL: ${{ secrets.TEST_REDIS_URL }}
32        run: |
33          go test -tags=integration -race ./internal/repository/... -timeout 120s

Perhatikan bahwa integration test dijalankan di merge queue, bukan di setiap PR — ini menjaga feedback PR tetap cepat sambil memastikan tidak ada konflik yang lolos saat merge final.


09.7 Status Check Naming Convention

Nama status check yang jelas memudahkan developer tahu persis apa yang gagal tanpa membuka log. Panduan berikut membandingkan nama yang baik dan yang harus dihindari.

text
 1Naming convention yang penting untuk status checks:
 2
 3FORMAT: "[Emoji] Description"
 4  ✅ "L1: Go Build, Test & Lint"
 5  ✅ "L2: Architecture Boundary Check"
 6  ✅ "Security: Secret Detection"
 7  ✅ "Final Quality Gate"
 8
 9HINDARI:
10  ❌ "build" — terlalu generic
11  ❌ "test-123" — tidak descriptive
12  ❌ "CI" — tidak ada informasi
13
14WHY: Nama yang jelas membantu developer tahu exactly apa yang fail
15tanpa harus click ke detail log.
16
17Di GitHub PR check list, developer melihat:
18  ✅ L1: Go Build, Test & Lint        (passed)
19  ✅ L2: Architecture Boundary Check  (passed)
20  ❌ Security: Static Analysis         (failed: 2 HIGH issues)
21  ✅ Final Quality Gate               (passed)
22
23Vs generic names:
24  ✅ build  (what build?)
25  ✅ lint   (which linter?)
26  ❌ check  (WHAT check??)

Investasi kecil pada penamaan yang deskriptif terbayar setiap hari: developer langsung tahu “Security: Static Analysis” yang gagal, bukan menebak-nebak apa arti “check” yang merah.


09.8 Emergency Override Protocol

Tidak ada sistem yang 100% sempurna — kadang CI gagal karena alasan di luar PR dan hotfix urgent tetap harus merge. Protokol berikut mengatur siapa yang boleh override, kapan, dan kewajiban setelahnya.

text
 1Tidak ada sistem yang 100% sempurna. Ada kalanya CI fail karena
 2alasan yang tidak berkaitan dengan PR (flaky test, outage, dll.)
 3dan PR tetap perlu di-merge (hotfix urgent).
 4
 5Emergency override policy:
 6
 7Siapa yang boleh override:
 8  - Tech Lead atau Engineering Manager
 9  - Bukan developer biasa (even senior)
10
11Kondisi override:
12  - Production down dan PR ini adalah fix-nya
13  - CI fail karena infrastructure issue (bukan code issue)
14  - Time-sensitive compliance requirement
15
16Cara override di GitHub:
17  1. PR author request override ke tech lead
18  2. Tech lead review diff secara manual
19  3. Tech lead gunakan "Bypass branch protection" (admin privilege)
20  4. Merge dengan note yang jelas
21
22Post-override mandatory actions:
23  1. Document dalam PR description: kenapa override dilakukan
24  2. Create issue untuk fix CI issue yang menyebabkan override
25  3. Run penuh verification setelah deploy
26  4. Post-mortem jika override terjadi karena CI infrastructure
27
28Tracking overrides:
29  GitHub audit log mencatat semua protection bypasses
30  Monthly review: berapa kali override terjadi?
31  Terlalu sering = CI reliability issue yang perlu di-fix

Yang membuat protokol ini sehat adalah akuntabilitas: setiap override tercatat di audit log dan di-review bulanan. Override yang terlalu sering adalah sinyal reliability CI yang perlu diperbaiki, bukan kebiasaan yang dinormalisasi.


09.9 Quality Gate Dashboard

Selain enforcement per PR, tim butuh visibilitas tren kesehatan pipeline. Workflow terjadwal berikut mengumpulkan metrik dari GitHub API, meminta AI merangkumnya, lalu mem-posting ke Slack setiap Senin.

yaml
 1# Scheduled job untuk generate weekly quality dashboard
 2
 3name: Weekly Quality Dashboard
 4on:
 5  schedule:
 6    - cron: '0 8 * * 1'  # Setiap Senin 08:00 UTC = 15:00 WIB
 7
 8jobs:
 9  dashboard:
10    runs-on: ubuntu-latest
11    env:
12      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
13    steps:
14      - uses: actions/checkout@v4
15
16      - name: Collect metrics dari GitHub API
17        uses: actions/github-script@v7
18        id: metrics
19        with:
20          script: |
21            const { data: runs } = await github.rest.actions.listWorkflowRuns({
22              owner: context.repo.owner,
23              repo: context.repo.repo,
24              workflow_id: 'ci.yml',
25              per_page: 50,
26              created: `>=${new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()}`
27            });
28
29            const successRate = runs.workflow_runs.filter(r => r.conclusion === 'success').length
30              / runs.workflow_runs.length * 100;
31
32            const avgDuration = runs.workflow_runs
33              .filter(r => r.conclusion !== null)
34              .map(r => (new Date(r.updated_at) - new Date(r.created_at)) / 1000 / 60)
35              .reduce((a, b) => a + b, 0) / runs.workflow_runs.length;
36
37            core.setOutput('success_rate', Math.round(successRate));
38            core.setOutput('avg_duration', Math.round(avgDuration));
39            core.setOutput('total_runs', runs.workflow_runs.length);
40
41      - name: Generate AI summary
42        run: |
43          METRICS="Pipeline success rate: ${{ steps.metrics.outputs.success_rate }}%
44          Average duration: ${{ steps.metrics.outputs.avg_duration }} menit
45          Total runs last week: ${{ steps.metrics.outputs.total_runs }}"
46
47          PROMPT="Generate weekly engineering quality digest.
48
49          Metrics:
50          $METRICS
51
52          Format sebagai bullet points yang actionable:
53          - Highlight jika success rate < 90%
54          - Highlight jika average duration > 10 menit
55          - Suggest improvements if needed
56
57          Tone: professional, concise"
58
59          SUMMARY=$(./scripts/claude-call.sh "$PROMPT" 300)
60
61          # Post ke Slack
62          curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
63            -H "Content-Type: application/json" \
64            -d "$(jq -n \
65              --arg summary "$SUMMARY" \
66              --arg rate "${{ steps.metrics.outputs.success_rate }}" \
67              --arg duration "${{ steps.metrics.outputs.avg_duration }}" \
68              '{
69                text: (":bar_chart: *Weekly CI Quality Dashboard*\n\nSuccess Rate: " + $rate + "%\nAvg Duration: " + $duration + " min\n\n" + $summary)
70              }'
71            )"

Dashboard mingguan ini mengubah metrik pipeline dari angka mentah menjadi digest yang bisa ditindaklanjuti — jika success rate turun di bawah 90%, tim langsung tahu ada masalah bypass atau reliability yang perlu ditangani.


09.10 Checklist: Rangkaian Quality Gate Lengkap

Untuk memastikan tidak ada yang terlewat, checklist berikut merangkum semua komponen yang harus ada setelah menyelesaikan rangkaian quality gate ini.

text
 1Setelah menyelesaikan rangkaian quality gate, semua ini harus sudah ada:
 2
 3Quality Gates:
 4□ AI code review (Copilot atau custom Claude bot)
 5□ Security audit (gosec + govulncheck + gitleaks)
 6□ Test coverage enforcement (per-layer thresholds)
 7□ Architecture linting (depguard + custom script)
 8□ Final aggregate quality gate (quality-gate binary)
 9
10Branch Protection:
11□ main branch protected (no direct push)
12□ Required status checks configured
13□ Required reviewers: 1+ approver
14□ Stale review dismissal enabled
15□ Linear history required
16
17CODEOWNERS:
18□ .github/CODEOWNERS dibuat
19□ Architecture files: senior architect
20□ CI/CD: DevOps team
21□ Domain layer: domain experts
22□ Service ownership: service teams
23
24Documentation:
25□ Emergency override protocol documented
26□ Status check naming convention documented
27□ Weekly quality dashboard running
28□ Pipeline health metrics tracked

Jika semua kotak sudah tercentang, tim punya sistem enforcement yang lengkap: setiap PR melewati gate yang sama, dengan akuntabilitas dan visibilitas yang jelas.


09.11 Tips & Gotchas

💡 Tip 1: Required check harus exact match nama job. “Final Quality Gate” harus benar-benar match dengan name: di YAML. Typo = check tidak pernah “required” tapi tidak blocking.

💡 Tip 2: if: always() untuk final gate. Final quality gate perlu tahu hasil dari semua gates bahkan yang fail. if: always() memastikan job tetap jalan.

💡 Tip 3: enforce_admins: false. Admin perlu bisa bypass untuk emergency. Tapi catat setiap bypass dengan audit trail.

💡 Tip 4: Test branch protection di staging dulu. Sebelum apply ke main, test di branch staging atau develop. Typo di branch protection bisa block semua developer.

⚠️ Gotcha 1: needs yang terlalu strict. Jika final gate needs semua jobs, satu optional job yang fail akan block final gate. Gunakan needs hanya untuk critical jobs.

⚠️ Gotcha 2: Forked PR tidak punya secrets. PR dari fork tidak bisa akses secrets (ANTHROPIC_API_KEY). Handle dengan if: github.event.pull_request.head.repo.full_name == github.repository.

⚠️ Gotcha 3: Status check yang flaky. Jika flaky test menyebabkan status check sering fail, itu adalah false positive yang erode trust. Fix flaky tests sebelum enforce sebagai required check.


09.12 Comparison: Before and After

Angka nyata memperjelas nilai dari sistem quality gate. Perbandingan berikut menunjukkan perubahan waktu review, jumlah isu, dan bug production sebelum dan sesudah lima quality gate diterapkan.

text
 1SEBELUM (baseline — tanpa quality gates):
 2  PR review: 1-2 hari
 3  Mechanical issues per PR: 8
 4  Overhead: 90 menit per PR (reviewer + dev fix)
 5  Post-deploy bugs: 4-6 per sprint
 6
 7SESUDAH (dengan 5 quality gates):
 8  Automated checks: 7 menit
 9  Mechanical issues yang lolos ke human: 2
10  Overhead: 27 menit per PR
11  Post-deploy bugs: 1-2 per sprint
12
13SAVINGS:
14  25 jam/minggu tim waktu → Rp 15 juta/bulan value
15  Cost pipeline: < Rp 150,000/bulan API calls

Rasio biaya-manfaatnya ekstrem: pipeline yang menghabiskan kurang dari Rp 150 ribu per bulan menghasilkan penghematan setara Rp 15 juta per bulan dalam waktu tim — belum menghitung bug production yang tercegah.


09.13 Enforcement Phase: Rollout Bertahap

Menyalakan enforcement penuh sekaligus adalah resep untuk resistensi tim. Tabel berikut memetakan fase rollout dari shadow mode sampai full enforcement.

PhaseDurationBlock onWarning on
ShadowMinggu 1–2Tidak adaSemua
SoftMinggu 3–4Secrets sajaSemua AI gate
StandardBulan 2L1 + Secrets + CRITICALAI suggestions
FullBulan 3+Semua gateTidak ada

Prinsipnya: mulai permisif, perketat berdasarkan data perilaku tim. Memulai langsung di “Full” menciptakan kebiasaan bypass yang sulit dibalik — buy-in tumbuh dari rollout yang bertahap, bukan dari paksaan mendadak.


09.14 Configuring Thresholds Per Branch

Standar kualitas tidak harus sama di semua branch. Snippet berikut menyetel threshold lebih ketat untuk main dibanding develop dan feature branch.

yaml
 1# main: strict
 2# develop: moderate
 3# feature: permissive
 4
 5- name: Set thresholds
 6  run: |
 7    case "${{ github.ref }}" in
 8      refs/heads/main)
 9        echo "AI_THRESHOLD=80" >> $GITHUB_ENV
10        echo "COVERAGE_THRESHOLD=85" >> $GITHUB_ENV ;;
11      refs/heads/develop)
12        echo "AI_THRESHOLD=70" >> $GITHUB_ENV
13        echo "COVERAGE_THRESHOLD=75" >> $GITHUB_ENV ;;
14      *)
15        echo "AI_THRESHOLD=60" >> $GITHUB_ENV
16        echo "COVERAGE_THRESHOLD=65" >> $GITHUB_ENV ;;
17    esac

Standar yang lebih tinggi untuk main (yang dipakai user) daripada develop (branch integrasi) daripada feature branch (pengembangan aktif) membuat enforcement proporsional dengan risiko rilis.


09.15 Branch Protection sebagai Kesepakatan Tim

Pipeline bukan alat pengawasan, melainkan kesepakatan tim yang di-encode dalam kode. Cara membingkainya ke tim menentukan apakah mereka patuh sukarela atau mencari celah.

“Kita sepakat bahwa kode yang mencapai main harus sudah di-review AI untuk isu mekanis, dicek untuk kerentanan keamanan, diverifikasi terhadap spec, dan disetujui oleh reviewer manusia.”

Bingkai sebagai “kita membangun guardrail yang melindungi semua orang dari kesalahan”, bukan “kita memonitor kualitas kodemu”. Tim yang memahami tujuannya patuh sukarela; tim yang merasa diawasi mencari workaround. Framing ini sama pentingnya dengan konfigurasi teknisnya.


09.16 Handling Fork PR

Pada proyek open source, PR dari fork tidak punya akses ke secrets seperti ANTHROPIC_API_KEY. Kondisi berikut memastikan job AI review di-skip untuk fork PR agar tidak gagal karena secret yang kosong.

yaml
 1# Fork PRs don't have access to secrets
 2# Skip AI review for forks, run standard checks only
 3
 4jobs:
 5  ai-review:
 6    if: |
 7      github.event_name == 'pull_request' &&
 8      github.event.pull_request.head.repo.full_name == github.repository &&
 9      !github.event.pull_request.draft
10    # Skips for: fork PRs, draft PRs

Untuk proyek open source, definisikan job “fork review” terpisah yang berjalan tanpa secrets dan memberi feedback terbatas — sehingga kontributor eksternal tetap mendapat sebagian nilai pipeline.


09.17 Flaky Test Management

Status check yang required tapi flaky justru merusak kepercayaan pada CI. Protokol berikut mengatur cara mendeteksi, mengkarantina, dan menghapus test yang tidak reliable.

text
1Detection: test gagal di CI tapi lolos saat dijalankan lokal
2Classification: "flaky" (tambahkan ke flaky-tests.txt)
3Quarantine: pindahkan ke job terpisah dengan continue-on-error: true
4Priority: fix dalam 2 sprint atau delete

Aturan pahitnya: required check yang gagal 10% dari waktu lebih buruk daripada tidak ada check sama sekali — ia melatih developer untuk mengabaikan kegagalan CI, yang menghancurkan seluruh nilai enforcement.


09.18 Cost Summary

Enforcement selengkap ini terdengar mahal, padahal biayanya kecil. Rincian berikut menunjukkan biaya bulanan nyata untuk tim 5 developer dengan 24 PR per minggu.

text
 1Monthly cost for 5-developer team, 24 PRs/week:
 2
 3GitHub Actions (compute):         ~$3/month
 4Anthropic API (haiku, all uses):  ~$2/month
 5GitHub Copilot Business:          $95/month (probably already paid)
 6
 7Total additional cost:            ~$5/month (excluding Copilot)
 8
 9Value:
10  25 hours/week saved in review overhead
11  ~70% reduction in post-deploy bugs
12  Full spec compliance enforcement
13  Architecture drift prevention
14  Automatic security scanning
15
16ROI: > 1000% on the $5/month API cost alone

Dengan biaya tambahan sekitar $5 per bulan di luar Copilot, ROI-nya melampaui 1000% — angka yang membuat pertanyaan “apakah worth it?” nyaris tidak relevan.


09.19 Ringkasan: Rangkaian Quality Gate

Rangkaian quality gate ini selesai dengan lima gate yang bekerja bersama:

AI code review: isu mekanis ditangkap sebelum reviewer manusia melihatnya, dengan scoring objektif dan graduated enforcement.

Security audit: secrets, CVE yang diketahui, static analysis, dan semantic security review berlapis.

Test coverage: per-layer thresholds, spec-to-AC mapping, dan gap analysis yang bermakna.

Architecture linting: Clean Architecture di-enforce otomatis di setiap PR.

Branch protection: aggregate scoring, required status checks, dan CODEOWNERS sebagai enforcement final.

End state-nya jelas: setiap developer, setiap PR, setiap hari melewati bar kualitas yang sama — terlepas dari siapa yang me-review, jam berapa, atau seberapa mepet deadline-nya.


09.20 Transisi ke Artikel Berikutnya

Sampai di sini, pipeline kita sudah pandai mengevaluasi kode: menilai, mem-block, dan memberi skor. Tapi pipeline yang matang tidak hanya menilai — ia juga menghasilkan artefak yang berguna bagi tim.

Di artikel berikutnya kita mulai fase automation: membuat pipeline meng-generate deskripsi PR secara otomatis dari spec dan diff, sehingga reviewer langsung paham konteks perubahan tanpa penulis harus menjelaskannya manual.

Artikel Terkait

💬 Komentar