실패 모드 하든(harden) 프로세스

ADR 0059 failure classifier + supply 2 대시보드를 닫힌 에러 루프(closed error loop) 로 바꾸는 monotone-harden 워크플로: 감사(audit)가 표면화하는 모든 실패 모드는 카테고리, eval 예제, 래칫(ratcheting) 천장을 얻는다 — 그래서 같은 회귀가 조용히 재발할 수 없다.

이것은 Phase 5 감사(#992)의 항목 3 이다. 계약은 ADR 0062 다.

Historical scope note. 이 문서의 legacy reports/real100/*, make real-eval, n=221, kordoc 계열 언급은 ADR 0062 당시 v1 private-eval ratchet 를 설명하는 archive-only 운영 기록이다. 현재 새 작업·PR·claim 의 private eval 근거는 real100_v2 표면만 사용해야 하며, 이 프로세스를 재활성화하려면 먼저 real100_v2 aggregate/gate 로 재검증해야 한다.

루프

audit surfaces a failure mode  (e.g. #1005 retrieval_miss, #1020 verifier_false_negative)
        │
        ▼
(a) category exists in failure_classifier.py?  ──no──▶  add category + lock test
        │ yes                                            (tests/test_failure_classifier.py)
        ▼
(b) ≥5 representative examples in real-eval set?  ──no──▶  add hardcase examples
        │ yes                                              (eval/real_config.local.yaml)
        ▼
(c) ceiling set in test_failure_rate_regression.py?  ──no──▶  set ceiling = current rate + margin
        │ yes
        ▼
(d) supply 2 dashboard renders it  (scripts/render_failure_distribution.py)
        │
        ▼
fix lands ──▶ lower committed rate ──▶ TIGHTEN ceiling in same PR ──▶ loop closes tighter

래칫은 한 방향으로만 돌아간다: 천장은 fix 가 landing 되면 내려가며, 명시적 [ALLOW_REGRESSION] 정당화 없이는 절대 올라가지 않는다.

이 방향성은 이제 CI 가 강제한다 — branch-and-issue-check.yml 의 ceiling-ratchet gate (scripts/check_branch_and_issue.py --check-ceiling-ratchet) 가 base 브랜치 대비 ceiling 상향/제거를 PR 본문의 [ALLOW_REGRESSION: <category> ...] 토큰 없이는 차단한다 (issue #1150). in-test test_ceilings_are_monotone_sane 은 역전(천장 < 현재 rate)만 가드하므로, 상향 차단은 이 CI gate 가 담당한다.

각 표면의 역할

surface file role
classifier eval/scorers/failure_classifier.py 7-category first-match-wins 라벨 (ADR 0059)
classifier lock tests/test_failure_classifier.py ordering 을 고정해 Finding #1 이 verifier_false_negative 로 유지되게 함
dashboard scripts/render_failure_distribution.py distribution + ADR 0059 계약 ✓ 렌더 (Markdown/aggregate JSON + local HTML board)
regression gate tests/test_failure_rate_regression.py 커밋된 baseline 의 래칫 천장 (ADR 0062)
ratchet gate scripts/check_branch_and_issue.py --check-ceiling-ratchet base 대비 ceiling 상향/제거를 [ALLOW_REGRESSION] 없이 차단 (CI, issue #1150)
baseline reports/real100/baseline.aggregate.json historical v1 archive-only aggregate. 현재 게이트/claim 에 재사용하려면 real100_v2 aggregate 로 재검증해야 함 (ADR 0005 경계)

새로 표면화된 실패 모드 추가하기

  1. 카테고리를 확정한다. 모드가 기존 7-category 라벨에 맞으면 단계 3 으로 건너뛴다. 진정으로 새것이라면 failure_classifier.py 의 FAILURE_CATEGORIES 와 classify_failure() 에 추가하되, first-match-wins ordering 을 존중한다(ADR 0059). tests/test_failure_classifier.py 에 유닛 테스트를 추가한다.

  2. ≥5 개 예제를 추가한다. 그 모드를 보이는 대표적인 hardcase 쿼리를 최소 5개 eval/real_config.local.yaml(gitignore, ADR 0005)에 추가한다. 이는 rate 에 안정적인 분모 신호를 준다.

  3. 천장을 설정한다. historical v1 절차는 baseline 을 regen(make real-eval + make real-eval-baseline-update STRICT=1)한 뒤 새 카테고리의 커밋된 rate 를 읽고, tests/test_failure_rate_regression.py 의 CEILING_RATE_BY_CATEGORY 에 current_rate + margin 을 추가했다. 이 명령 조합과 v1 rate 는 현재 새 작업에서는 archive-only 이며, 현재 천장/claim 을 갱신하려면 먼저 real100_v2 aggregate/gate 로 등가 표면을 확정해야 한다. margin 은 variance 감사(#1025)가 측정한 cross-HEAD variance 를 흡수했다 — 최초 도입 시에는 넉넉히 설정한 뒤 조였다(tighten).

  4. 대시보드가 렌더하는지 검증한다. historical v1 절차에서는 scripts/render_failure_distribution.py 를 재실행해 새 카테고리가 reports/real100/failure_distribution.md 와 local-only reports/real100/failure_distribution.html 에 나타나는지 확인했다. 현재 표면에서 같은 주장을 하려면 real100_v2 aggregate 기반 출력으로 먼저 대체해야 한다.

fix 이후 천장 조이기

historical v1 fix PR 이 게이트된 rate 를 낮출 때의 절차는 다음과 같았다:

  1. fix 의 HEAD 에서 v1 baseline 을 regen 한다.
  2. 카테고리의 CEILING_RATE_BY_CATEGORY 항목을 새 current_rate + small_margin 으로 같은 PR 에서 낮춘다.
  3. test_ceilings_are_monotone_sane 은 천장을 현재 rate 아래로 설정하는 것(역전된 래칫)을 가드한다.

현재 real100_v2 표면에서 같은 래칫을 운용하려면 v2 baseline/gate 를 먼저 확정한 뒤 그 표면의 rate 에만 적용해야 한다.

의도적으로 천장을 상향해야 한다면 (예: ADR 0058 같은 cross-HEAD 변경으로 variance 가 커진 경우), PR 본문에 [ALLOW_REGRESSION: <category> 0.X→0.Y 사유] 토큰을 추가한다 — CI ceiling-ratchet gate 가 이를 확인하고, 없으면 PR 을 차단한다.

절대 카운트가 아닌 rate 인 이유

variance 감사(#1025)는 절대 카운트가 HEAD 별로는 결정론적이지만 cross-HEAD 로는 변동함을 발견했다(verifier_false_negative 가 PR #1001 / #1004 / #1018 전반에 49 ↔ 65 ↔ 76 으로 변동, 반면 same-HEAD N=3 실행은 byte-identical 이었다). 문서화된 margin 이 있는 rate 는 그 cross-HEAD variance 를 흡수한다; 히스토리컬 variance 를 넘어선 진짜 회귀는 여전히 게이트를 발화시킨다. fix 의 전/후는 항상 같은 커밋에서 비교하라 — cross-HEAD 비교는 fix 를 그 사이의 변경(예: ADR 0058 hybrid 전환)과 혼동시킨다.

워크 예제 (이 루프를 먹인 historical v1 감사들)

audit mode committed rate ceiling
#1020 verifier_false_negative 0.344 (76/221) 0.40
#1005 retrieval_miss 0.290 (64/221) 0.34
— total failures 0.814 (180/221) 0.86

각 감사의 follow-up fix(Issue F verifier 하든, Issue A top_k ablation, …)는 이 historical v1 rate 를 낮춘 뒤 천장을 조이는 것을 목표로 했다. 현재 작업에서 같은 결론을 주장하려면 real100_v2 evidence 로 다시 측정해야 한다.