0062: Failure-rate regression contract (Phase 5 supply 3)
0062: Failure-rate regression contract (Phase 5 supply 3)
- Status: proposed
- Date: 2026-05-19
- Deciders: Hyunsoo Kim
- Related: ADR 0059 (failure-mode classifier), Phase 5 audit (#992) item 3, supply 2 dashboard (PR #1004), audits #1005 / #1020 / #1025, issue #1066
Context
ADR 0059 (PR #1001) 가 7-카테고리 failure_classifier.py 를 도입했고
supply 2 dashboard (PR #1004) 가 그 분포를 렌더링한다. 이어 3건의 감사(audit)가
지배적 mode 를 정량화 — retrieval_miss=83 (#1005),
verifier_false_negative=76 (#1020), 그리고 cross-HEAD 분산(variance) 출처
(#1025). 그러나 Phase 5 audit (#992) item 3 “closed error loop” 는
여전히 absent: 이 mode 들의 silent 회귀(regression)를 막는 것이 없다.
audit 가 표현했듯이 (docs/audits/eval-framework-phase5-audit.md), finding 이
“어디에도 누적 안 됨 — 다음 분기에 같은 신호가 나와도 다시 raw inspection으로
발견” — 신호가 ratchet 되지 않으므로 같은 회귀가 탐지되지 않은 채
재발할 수 있다.
variance audit (#1025) 는 또한 절대 failure count 가 HEAD 별로는 deterministic 이지만 cross-HEAD 로는 변동함을 확립했다(verifier_false_negative 가 PR #1001 / #1004 / #1018 에 걸쳐 49 ↔ 65 ↔ 76 범위). 어떤 ceiling 계약이든 매 baseline regen 마다 flap 하는 대신 그 문서화된 cross-HEAD 분산을 흡수해야 한다.
Decision
세 부분으로 구성된 failure-rate regression contract 를 도입한다:
-
tests/test_failure_rate_regression.py가 커밋된reports/real100/baseline.aggregate.json(aggregate-only, ADR 0005 경계) 를 읽고, gate 대상 각 카테고리의 failure RATE (count / num_predictions) 가 monotone-ratchet ceiling 아래에 머무는지 단언(assert)한다. 현재 gate 대상:total_failure_rate ≤ 0.86,verifier_false_negative ≤ 0.40,retrieval_miss ≤ 0.34. ceiling 은 #1025 가 측정한 cross-HEAD 분산을 흡수하도록 현재 커밋된 rate 위에 margin 을 둔다. Issue F/G/A-class 수정이 land 함에 따라 아래로만 ratchet 가능 — gate 대상 rate 를 ceiling 너머로 악화시키는 baseline regen 은 tighten-or-justify 해야 한다(테스트가 실패해 인지를 강제), 절대 silent 회귀하지 않는다. -
docs/operations/failure-mode-harden-process.md가 monotone-harden 워크플로를 문서화한다: audit 가 새 failure mode 를 표면화하면 → (a)failure_classifier.py에 그 카테고리 추가/확인 + lock 테스트, (b) real-eval set 에 대표 예시 ≥5 추가, (c)test_failure_rate_regression.py에서 그 ceiling 설정 또는 tighten, (d) supply 2 dashboard 가 렌더링. 이것이 closed loop 다. -
scripts/check_branch_and_issue.py --check-ceiling-ratchet(CI 의branch-and-issue-check.yml에 wired) 가 커밋된 ceiling 을 base 브랜치와 diff 하여, gate 대상 ceiling 을 상향하거나 gated 카테고리를 제거하는 PR 을 PR 본문에 명시적[ALLOW_REGRESSION: <category> ...]토큰이 없으면 실패시킨다. 이것이 “정당화 없이는 절대 올라가지 않는다” 를 운영자 규율이 아닌 실제 gate 로 만든다. in-testtest_ceilings_are_monotone_sane은 ceiling 을 현재 rate 아래로 두는 역전(inversion)만 가드하며 상향 자체는 잡지 못하므로, 이 CI gate 가 그 갭을 닫는다 (issue #1150).
테스트는 또한 ADR 0059 first-match 계약
(verifier_false_negative == abstention_outcomes.incorrect_answer) 을 재단언하여
이를 깨뜨리는 향후 ordering 변경도 regression gate 를 실패시키도록 한다.
CI 강제 vs 운영자(operator) 실행 (정직한 경계). CI 가 강제하는 것:
(i) 커밋된 baseline 의 rate ≤ ceiling (위 pytest gate — 커밋된 aggregate 를
읽으므로 private 데이터 없이 CI 에서 실행), (ii) ceiling ratchet (위 #3 CI gate
— 정당화 없는 ceiling 상향/제거 차단). CI 가 강제하지 못하는 것:
head-vs-baseline 회귀 탐지 — “이 코드 변경이 실제로 failure mode 를
악화시켰는가”. real-eval 은 private-local (ADR 0005) 이라 CI 에 head eval 을
실행할 수 없다. 그 탐지는 운영자가 make real-eval + make real-eval-delta 를
돌리는 단계이며, delta 는 failure_category_counts 도 표면화한다
(scripts/run_real_eval_delta.py). load-bearing 변경의 §5b 요구가 운영자에게
그 실행을 촉구하는 discipline hook 이다. 잔여 갭(residual gap): failure mode 를
악화시키지만 baseline 을 regen 하지 않는 코드 변경은 CI 에 보이지 않는다 —
폐루프는 운영자가 real-eval 을 돌려 회귀가 커밋된 baseline 에 반영될 때 비로소
ceiling 테스트가 발화하는 것에 의존한다.
production code path 변경 없음 (rag_*.py, api/, eval/config.yaml
미터치). 테스트는 커밋된 baseline 의 read-only consumer 이며, private 데이터
없이 CI 에서 실행된다. ceiling-ratchet gate 도 커밋된 ceiling 소스만 비교하므로
마찬가지로 private 데이터 없이 동작한다.
Consequences
- Phase 5 audit item 3 (✗ absent → ✓ present) — closed error loop:
지배적 failure mode 가 이제 측정만 되는 게 아니라 gate 된다.
verifier_false_negative또는retrieval_miss가 ceiling 을 넘는 silent 회귀는 Pytest gate 를 red 로 만든다. - Phase 5 supply trilogy (1 classifier / 2 dashboard / 3 regression contract) 가 완성됨; portfolio narrative 가 “측정 → 분류 → dashboard → ceiling 잠금” loop 를 닫는다.
- 향후 수정 PR (Issue F verifier hardening / Issue A top_k ablation / Issue G multi-doc topic spread) 은 구체적 ratchet 목표를 가진다: 커밋된 rate 를 낮추고, 같은 PR 에서 ceiling 을 tighten.
- 비용: gate 대상 rate 를 악화시키는 baseline regen 은 이제 수정이나
명시적
[ALLOW_REGRESSION]ceiling bump 중 하나가 필요하고, ceiling 상향/제거는 PR 본문의[ALLOW_REGRESSION: <category> ...]토큰을 CI 가 확인한다 (issue #1150) — 설계상 regen PR 에 약간 더 많은 마찰. - ADR 0001 (naive_baseline byte-identical) / 0003 (answer dict) / 0005 (eval-split aggregate-only) / 0059 (classifier additive) 불변식 모두 보존.
Alternatives considered
- 절대 count 로 gate — 기각: variance audit (#1025) 가 count 는 cross-HEAD 로 이동함을 보임; 문서화된 margin 을 가진 rate 가 더 안정적.
- 모든 카테고리 gate — v1 에서는 기각: planner/generator/context 카테고리는 near-zero 이고 noisy 함; 2개 지배적 mode + total 을 gate 하는 것이 high-signal subset. audit 가 표면화함에 따라 카테고리 추가 (그것이 바로 harden process).
- 테스트 내에서 real-eval 실행 — 기각: private 데이터는 CI 에 들어올 수 없음 (ADR 0005). 커밋된 aggregate 를 읽으면 gate 가 CI-runnable 하고 deterministic 하게 유지된다.