Skip to content

Commit a7909dc

Browse files
authored
(#66) M5-1~M5-3 품질게이트 및 런타임 안전성 강화
* docs: M5 품질 자동화 로드맵과 실행 계획 추가 - Harness roadmap/playbook에 M5 단계 및 task 시퀀스 추가 - M5 active plan 문서 생성 및 검증 계획 기록 - AGENTS/testing runbook/PR 템플릿을 coverage 검증 흐름에 맞게 갱신 * ci: 커버리지 품질 게이트와 배포 헬스체크 강화 - JaCoCo 라인 커버리지 최소 기준(45%) 검증 태스크 추가 - PR용 quality-gate 워크플로우 추가 및 리포트 아티팩트 업로드 - 배포 후 HTTP 200 + status=UP 조건으로 헬스체크를 엄격화 - prod profile actuator 노출 범위를 최소화 * refactor: username 로그를 기본 마스킹하도록 정렬 - LogSanitizer 도입 및 LogContext username/target_username 자동 마스킹 적용 - 주요 plain logger 경로에서 raw username 출력을 마스킹 값으로 교체 - LogContext/LogSanitizer 테스트 추가와 logging contract 문서 동기화 * fix: M5 PR 리뷰 지적사항 반영 - deploy health check를 단일 curl+timeout 방식으로 보강 - prod actuator 노출/보안 정책을 health 중심으로 축소 - 예외 메시지 username은 hash 기반으로 전환 - short username 마스킹 및 로그 테스트 검증 강화 * fix: prod Prometheus 스크레이프 경로를 재노출 - application-prod에서 actuator 노출에 prometheus를 복원 - SecurityConfig에 /actuator/prometheus permit 경로를 추가 - health 강화는 유지하고 info 노출은 계속 비활성화
1 parent 85f4699 commit a7909dc

23 files changed

Lines changed: 341 additions & 23 deletions

.github/ISSUE_TEMPLATE/harness_epic.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ body:
1313
id: phase
1414
attributes:
1515
label: Phase
16-
description: 예) M1 Legibility / M2 Observability / M3 System of Record / M4 Feedback Loop
16+
description: 예) M1 Legibility / M2 Observability / M3 System of Record / M4 Feedback Loop / M5 Quality Automation
1717
placeholder: "M1 Legibility"
1818
validations:
1919
required: true

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
| Build | `./gradlew build -x test` | |
2727
| Unit | `./gradlew test` | |
2828
| Integration | `./gradlew integrationTest` 또는 `미실행(사유)` | |
29+
| Quality (Coverage) | `./gradlew test jacocoTestCoverageVerification` 또는 `미실행(사유)` | |
2930

3031
## 7) 관측성 확인
3132
- 확인한 로그:

.github/workflows/deploy.yml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,26 @@ jobs:
8989
echo "Waiting for application to start..."
9090
sleep 45
9191
92+
HEALTH_URL="${{ secrets.BACKEND_HEALTH_URL }}"
93+
if [ -z "$HEALTH_URL" ]; then
94+
echo "BACKEND_HEALTH_URL secret is required."
95+
exit 1
96+
fi
97+
echo "Health URL: $HEALTH_URL"
98+
9299
for i in {1..5}; do
93-
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.BACKEND_URL }}" || echo "000")
94-
if [ "$HTTP_STATUS" != "000" ]; then
95-
echo "Health check passed! (HTTP $HTTP_STATUS)"
100+
RESPONSE=$(curl -sS --connect-timeout 5 --max-time 10 -w $'\n%{http_code}' "$HEALTH_URL" || true)
101+
HTTP_STATUS=$(printf '%s' "$RESPONSE" | tail -n1)
102+
RESPONSE_BODY=$(printf '%s' "$RESPONSE" | sed '$d')
103+
104+
if [ "$HTTP_STATUS" = "200" ] && echo "$RESPONSE_BODY" | grep -Eq '"status"[[:space:]]*:[[:space:]]*"UP"'; then
105+
echo "Health check passed! (HTTP 200, status=UP)"
96106
exit 0
97107
fi
98-
echo "Attempt $i/5 failed, retrying in 15s..."
108+
echo "Attempt $i/5 failed. HTTP=$HTTP_STATUS, body=$RESPONSE_BODY"
109+
echo "Retrying in 15s..."
99110
sleep 15
100111
done
101112
102-
echo "Health check failed! Server not responding."
113+
echo "Health check failed! Expected HTTP 200 with status=UP."
103114
exit 1

.github/workflows/quality-gate.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Quality Gate
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- develop
8+
9+
jobs:
10+
coverage:
11+
name: Coverage Verification
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Java 21
19+
uses: actions/setup-java@v4
20+
with:
21+
java-version: '21'
22+
distribution: 'temurin'
23+
cache: 'gradle'
24+
25+
- name: Grant execute permission for gradlew
26+
run: chmod +x gradlew
27+
28+
- name: Run unit tests with coverage verification
29+
run: ./gradlew test jacocoTestReport jacocoTestCoverageVerification
30+
31+
- name: Upload JaCoCo HTML report
32+
uses: actions/upload-artifact@v4
33+
if: always()
34+
with:
35+
name: jacoco-html-report
36+
path: build/reports/jacoco/test/html/
37+
retention-days: 7
38+
39+
- name: Upload JaCoCo XML report
40+
uses: actions/upload-artifact@v4
41+
if: always()
42+
with:
43+
name: jacoco-xml-report
44+
path: build/reports/jacoco/test/jacocoTestReport.xml
45+
retention-days: 7

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Operating guide for humans and AI agents in `git-ranker`.
3939
- Build: `./gradlew build -x test`
4040
- Unit test: `./gradlew test`
4141
- Integration test: `./gradlew integrationTest`
42+
- Coverage verify: `./gradlew test jacocoTestCoverageVerification`
4243
- Single unit class: `./gradlew test --tests "com.gitranker.api.domain.user.service.UserRegistrationServiceTest"`
4344
- Single integration class: `./gradlew integrationTest --tests "com.gitranker.api.domain.user.UserRepositoryIT"`
4445
- Local run: `./gradlew bootRun`

build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '3.4.0'
44
id 'io.spring.dependency-management' version '1.1.6'
5+
id 'jacoco'
56
}
67

78
group = 'com.gitranker'
@@ -75,3 +76,30 @@ tasks.register('integrationTest', Test) {
7576
// Docker 29+는 최소 API 1.44를 요구하지만 docker-java 3.4.0은 기본값 1.32로 요청함
7677
systemProperty 'api.version', '1.44'
7778
}
79+
80+
jacoco {
81+
toolVersion = '0.8.12'
82+
}
83+
84+
tasks.named('jacocoTestReport') {
85+
dependsOn tasks.named('test')
86+
reports {
87+
xml.required = true
88+
html.required = true
89+
csv.required = false
90+
}
91+
}
92+
93+
tasks.named('jacocoTestCoverageVerification') {
94+
dependsOn tasks.named('test')
95+
violationRules {
96+
rule {
97+
element = 'BUNDLE'
98+
limit {
99+
counter = 'LINE'
100+
value = 'COVEREDRATIO'
101+
minimum = 0.45
102+
}
103+
}
104+
}
105+
}

docs/harness/issue-pr-playbook.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Use this playbook to execute harness phases with consistent granularity.
1515
- `harness/m4-1-pr-guardrail`
1616
- `harness/m4-2-archunit-guardrail`
1717
- `harness/m4-3-weekly-scorecard`
18+
- `harness/m5-1-coverage-quality-gate`
19+
- `harness/m5-2-deploy-health-hardening`
20+
- `harness/m5-3-pii-safe-logging-defaults`
1821

1922
## Commit Rule
2023
1. One commit = one intent.
@@ -41,6 +44,7 @@ Use this playbook to execute harness phases with consistent granularity.
4144
2. M2-1 -> M2-2 -> M2-3
4245
3. M3-1 -> M3-2 -> M3-3
4346
4. M4-1 -> M4-2 -> M4-3
47+
5. M5-1 -> M5-2 -> M5-3
4448

4549
## Ready-to-Create Issue Titles
4650
1. `[Harness Epic]: M1 Increase Application Legibility`
@@ -59,3 +63,7 @@ Use this playbook to execute harness phases with consistent granularity.
5963
14. `[Harness Task]: M4-1 Enforce PR contract in CI`
6064
15. `[Harness Task]: M4-2 Add architecture guardrail tests`
6165
16. `[Harness Task]: M4-3 Add weekly scorecard and drift review`
66+
17. `[Harness Epic]: M5 Raise Verification Confidence`
67+
18. `[Harness Task]: M5-1 Add coverage quality gate`
68+
19. `[Harness Task]: M5-2 Harden deployment health checks`
69+
20. `[Harness Task]: M5-3 Enforce PII-safe logging defaults`

docs/harness/roadmap.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,31 @@ Execution unit is always small, reviewable PRs with explicit intent and evidence
9797
- Suggested commits:
9898
- `docs(scorecard): add measurement template`
9999

100+
## Phase 5: Quality Automation and Runtime Safety
101+
102+
### Epic Issue
103+
- Title: `Harness M5 - Raise Verification Confidence`
104+
- Goal: turn quality assumptions into automated checks for coverage, runtime safety, and PII-safe logging.
105+
106+
### Child Issues and PR Plan
107+
1. Issue: `M5-1 Add coverage quality gate`
108+
- PR: `ci(quality): enforce baseline unit-test coverage with report artifacts`
109+
- Suggested commits:
110+
- `build(quality): add jacoco coverage verification tasks`
111+
- `ci(quality): add pull-request quality gate workflow`
112+
113+
2. Issue: `M5-2 Harden deployment health checks`
114+
- PR: `ci(deploy): require strict health status after deployment`
115+
- Suggested commits:
116+
- `ci(deploy): require HTTP 200 and UP status for health checks`
117+
- `config(prod): reduce actuator endpoint exposure`
118+
119+
3. Issue: `M5-3 Enforce PII-safe logging defaults`
120+
- PR: `refactor(logging): mask username fields by default in log context`
121+
- Suggested commits:
122+
- `refactor(logging): add username masking sanitizer`
123+
- `test(logging): add masking behavior tests`
124+
100125
## Operating Rules
101126
1. Every child issue must map to one primary PR.
102127
2. PRs should target one outcome and avoid mixed concerns.

docs/observability/logging-contract.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
## 2) Scope
88
- Applies to structured logs emitted through `LogContext.event(...)`.
99
- Plain `log.debug(...)` calls should be migrated to `LogContext` incrementally.
10+
- `username` and `target_username` fields are masked by default through `LogSanitizer`.
1011

1112
## 3) Required Fields
1213

@@ -29,8 +30,8 @@
2930
| `username` | PII. Masking or anonymization is required (see Section 6.3). Prefer `maskUsername` or `hashUsername` before logging. |
3031

3132
Example (recommended):
32-
- `log_username_masked = maskUsername(username)` -> `te****r`
33-
- `log_username_hash = hashUsername(username)` -> `7f0c...`
33+
- `log_username_masked = maskUsername(username)` -> `te****`
34+
- `log_username_hash = hashUsername(username)` -> `9bba5c53a054`
3435

3536
Verification checklist:
3637
1. If `username` appears in a log field, confirm `maskUsername` or `hashUsername` is applied.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Plan: 2026-02-25-m5-1-quality-gates-and-runtime-safety
2+
3+
## 1) Purpose
4+
- Problem: workflow/process guardrails exist, but code-level quality confidence is still weak.
5+
- Intended outcome: add automated coverage verification, stricter deployment health checks, and PII-safe logging defaults.
6+
- Non-goals: redesign business domain flows or replace deployment architecture.
7+
8+
## 2) Scope
9+
- In scope:
10+
- add JaCoCo verification tasks and PR quality workflow
11+
- harden deployment health check condition
12+
- reduce production actuator exposure defaults
13+
- enforce username masking defaults in logging context and high-risk raw logs
14+
- Out of scope:
15+
- full migration of every plain logger call to structured logging
16+
- introducing external quality SaaS (Sonar, Codecov)
17+
18+
## 3) Progress
19+
- [x] add M5 roadmap/playbook entries
20+
- [x] add coverage gate in Gradle and CI workflow
21+
- [x] harden deploy health check and production actuator scope
22+
- [x] apply username masking defaults and add tests
23+
- [x] run validation commands and record outcomes
24+
25+
## 4) Design Notes
26+
- Constraints: keep the existing `build/test/integrationTest` split intact.
27+
- Tradeoffs: start with a realistic baseline coverage threshold to avoid blocking all PRs.
28+
- Open questions: final target coverage threshold after two to three weekly scorecards.
29+
30+
## 5) Decision Log
31+
- 2026-02-25: M5 focuses on automated quality confidence, not process expansion.
32+
- 2026-02-25: set initial line coverage gate at 45% to enforce a meaningful floor while staying below current baseline.
33+
34+
## 6) Validation Plan
35+
- Required commands:
36+
- `./gradlew test`
37+
- `./gradlew test jacocoTestCoverageVerification`
38+
- Observability checks:
39+
- verify logging contract and runtime behavior align for username masking
40+
41+
## 7) Risks and Rollback
42+
- Risks: baseline threshold may be too strict or too lenient for current suite.
43+
- Rollback strategy:
44+
- lower only the numeric coverage threshold in `build.gradle`
45+
- disable strict deploy health check condition in `deploy.yml` if false positives occur
46+
47+
## 8) Result Snapshot
48+
- `./gradlew build -x test` passed
49+
- `./gradlew test` passed
50+
- `./gradlew test jacocoTestReport jacocoTestCoverageVerification` passed
51+
- `./gradlew integrationTest` is environment-dependent (Docker/Testcontainers required)

0 commit comments

Comments
 (0)