
---

# task-2723+3 — critical_gap zero-count REDESIGN (severity-count 구조 파싱, 회장 승인)

## Situation
round-1~3 의 zero-count 판정은 "줄에 zero 표현(0건/=0/없음)이 있으면 줄 전체를 suppress" 하는 라인 단위 휴리스틱이었다. 3회 진동(over-broad → word-boundary → mixed-count) 끝에 LOOP_BOUNDARY. 회장이 추가 regex 한 줄 incremental patch를 금지하고 **구조적 재설계**를 승인했다.

## Complication
mixed-count 한 줄 `"CRITICAL: 0, HIGH: 1"` 에서 라인 전체 suppress 는 HIGH 1(실제 미해결)까지 통째로 억제(false-negative)한다. 줄 단위 판정 자체가 한계.

## Question
줄을 통째로 보지 않고, **줄에서 severity별 (severity, count) 쌍을 파싱**하여 "모든 count 0 → 억제, 하나라도 >0 → 탐지 유지"로 전환할 수 있는가?

## Answer (redesign 구현)
`_is_zero_count_context(line)` 를 5단계 구조 파싱으로 재구현:
1. **STRONG_ISSUE_MARKER** (`[CRITICAL]`/`[HIGH]`/`![critical|high]`/`severity:critical|high`) → `return False` (실제 finding).
2. **VULN_NEGATION_DENYLIST** (인증/권한/암호화/패치/수정/조치/해결 없음, secure_mode|timeout|port=0) → `return False`.
3. **`parse_severity_counts(line)`** (신규 헬퍼): `_ZC_BIND_RE`로 severity 클러스터(슬래시 결합 그룹)에 결합된 숫자를 좌→우 추출.
   - `"CRITICAL: 0, HIGH: 1"` → `[0, 1]`, `"CRITICAL 0개, HIGH 2개"` → `[0, 2]`, `"HIGH/CRITICAL 0건"` → `[0]`, `"fresh unresolved HIGH/CRITICAL = 0"` → `[0]`, `"CRITICAL=0"` → `[0]`.
   - `if counts: return all(c == 0 for c in counts)` → **하나라도 >0 이면 False(탐지 유지)**.
4. **숫자 없는 부재형**: COUNT_ANCHOR + (ABSENCE_EXPR `없음|없습니다|없다`) → True. 단독 zero-count(`0건/0개`가 유일 카운트, >0 카운트 부재) → True.
5. 그 외 → `return False`.

★ 핵심 차이: round-1~3 = "zero 표현 있으면 통째 suppress" / redesign = "모든 severity count 0일 때만 suppress". → `"CRITICAL: 0, HIGH: 1"` 의 HIGH 1 을 정확히 탐지(FAIL).

## 수정 파일 (expected_files 2개 내부 — 그 외 0)
1. `teams/shared/verifiers/critical_gap.py` — 구 `_ZC_ZERO_EXPR` 라인 suppress 휴리스틱 폐기, `parse_severity_counts` + `_ZC_BIND_RE`/`_ZC_GAP`/`_ZC_SEVERITY_CLUSTER`/`_ZC_ABSENCE_EXPR`/`_ZC_ZERO_UNIT`/`_ZC_NONZERO_UNIT` 신설, `_is_zero_count_context` 5단계 재구현. verify() public interface/schema 불변.
2. `tests/regression/test_critical_gap_false_positive_2506.py` — R4 블록 9건 추가(parse 단위 3 + _is_zero_count_context mixed/all-zero 3 + e2e verify FAIL/PASS 3). 기존 43건 전부 유지.

## 테스트 결과
- `python3 -m pytest tests/regression/test_critical_gap_false_positive_2506.py -q` → **52 passed** (기존 43 + 신규 R4 9).
  - PASS(억제 보존): HIGH/CRITICAL 0건, CRITICAL=0, fresh unresolved=0, 신규 HIGH 없음, 총0건, 이슈없음/이슈없다, HIGH:0/CRITICAL:0.
  - FAIL(탐지 유지): **CRITICAL: 0, HIGH: 1**(mixed), **CRITICAL 0개, HIGH 2개**(mixed), [CRITICAL] 인증/권한/암호화 없음, [HIGH] secure_mode=0/timeout=0, [CRITICAL] port=0, 패치 없음, 기존 TP/ZC 탐지력 전부 보존.

## L1 스모크테스트 결과 (필수)
- **서버 재시작**: 해당없음 (verifier 모듈 — 상주 서버/HTTP 없음, CLI 호출형).
- **API 응답 확인**: 해당없음 (curl 대상 엔드포인트 없음). 대신 **모듈 실로드 e2e**: 워크트리(=PR 코드)의 `critical_gap.py`를 `importlib`로 직접 로드 → `parse_severity_counts`/`_is_zero_count_context` 실호출.
  - `parse('CRITICAL: 0, HIGH: 1')` = `[0, 1]`, `parse('CRITICAL 0개, HIGH 2개')` = `[0, 2]` ✓
  - mixed `CRITICAL: 0, HIGH: 1` → False(탐지) ✓ / `CRITICAL 0개, HIGH 2개` → False(탐지) ✓
  - all-zero `HIGH/CRITICAL 0건` → True(억제) ✓ / `CRITICAL=0`,`fresh ... =0`,`신규 HIGH 없음`,`총0건`,`이슈없음` → True ✓
  - `[CRITICAL] 인증 없음` → False(strong+deny) ✓
  - **L1 RESULT: PASS** (pytest와 별개로 실제 모듈 실행 확인. pytest PASS ≠ 실동작이므로 별도 e2e 수행)
- **스크린샷**: 해당없음 (CLI/모듈 — UI 없음).

## 검증 조건 충족
1. redesign regression 전부 의도대로 PASS (52 passed: PASS 케이스 + mixed-count FAIL 2 + 기존 FAIL + 탐지력 보존) ✓
2. `git diff --name-only origin/main` = expected_files 2파일 내부 ✓
3. forbidden 0 (FORBIDDEN_CLEAN) ✓
4. ANU key full literal(`c119085...`) 0 — 두 파일 KEY_CLEAN ✓
5. verify interface/schema 불변 (`def verify(task_id, report_path="") -> dict`, `{"status","details"}`) ✓

## 머지 판단 (+3 redesign)
- **머지 필요**: Yes (단, merge_policy=none — ★ 본 task에서 merge 금지)
- **브랜치**: task/task-2723-dev2 (new head `822c0c0b`)
- **워크트리 경로**: /home/jay/workspace/.worktrees/task-2723-dev2
- **머지 의견**: 구조적 redesign으로 round-1~3 진동 근본 해소. expected_files 2개 내부, 회귀 52건 PASS(mixed-count FAIL 정확), L1 e2e PASS. ★ merge 금지 — ANU/watcher가 new head(`822c0c0b`) OWNER `/gemini review` → CI/Gemini watcher → MERGE_READY_CANDIDATE 판정·보고. 실제 merge는 회장 승인.

## doctrine 준수 (+3)
- same-PR post-Gemini push: redesign = 새 commit 2건(`a2d1c429`, `822c0c0b`)→new head→**non-force push**(`a2783b39..822c0c0b` fast-forward), amend 미사용 ✓
- ★ 사후 정책: 이후에도 같은 critical_gap zero-count 계열 HIGH/CRITICAL 재발 시 **추가 패치 금지** → PR #169 hold 또는 alternative resolver 보고. merge 회장 승인 전 금지 ✓
- bot `/gemini review` 무효(인간 OWNER 1회만), long polling 금지(watcher 위임) ✓

## 모델 사용 기록 (+3)
- 토르(백엔드, `_is_zero_count_context` 구조 파싱 재구현 + parse_severity_counts): sonnet
- 헤임달(테스터, R4 회귀 9건 append + 전체 pytest): sonnet
- 통합/독립 재검증/커밋/push/L1 e2e/보고: 오딘(Opus, 팀장)
- haiku 미사용 (정규식 구조 파싱 정합성·탐지력 보존 정밀도 요구)

## 팀장 검수 (자체 평가)
- 오딘(개발2팀장) 시각 검수 완료. 셀프 QC 8항목 통과: (1)요구사항 일치 (2)expected_files 2개만 수정 (3)테스트 52 PASS (4)L1 e2e PASS (5)ANU key literal 0 (6)forbidden 0 (7)verify interface/schema 불변 (8)회귀 탐지력 보존.
- 회장 명시 redesign 조건(라인 suppress → severity-count 구조 파싱, mixed-count FAIL 2건) 모두 충족.
