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.
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.
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 scriptDari 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.
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.
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 fiif: 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.
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 → EditUntuk tim yang menerapkan GitOps, konfigurasi branch protection sebaiknya juga versioned. Workflow berikut menerapkan aturan yang sama lewat GitHub Actions dengan trigger manual.
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.
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/ @catalogteamDengan 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.
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 120sPerhatikan 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.
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.
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-fixYang 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.
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.
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 trackedJika 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.
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 callsRasio 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.
| Phase | Duration | Block on | Warning on |
|---|---|---|---|
| Shadow | Minggu 1–2 | Tidak ada | Semua |
| Soft | Minggu 3–4 | Secrets saja | Semua AI gate |
| Standard | Bulan 2 | L1 + Secrets + CRITICAL | AI suggestions |
| Full | Bulan 3+ | Semua gate | Tidak 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.
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 esacStandar 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.
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 PRsUntuk 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.
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 deleteAturan 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.
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 aloneDengan 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.