eval-framework-progressive-audit — Phase 5 (닫힌 오류 루프, closed error loop)
eval-framework-progressive-audit — Phase 5 (닫힌 오류 루프, closed error loop)
Historical snapshot note. This audit records 2026-05 legacy
real100/ n=221 closed-error-loop findings. Treatreports/real100/*references as archive-only context; new task, PR, claim, and handoff evidence must usereal100_v2aggregate evidence plusmake real-eval-v2-check,make real-eval-v2-inventory, andmake real-eval-v2-guard.
| field | value |
|---|---|
| Skill | .claude/skills/eval-framework-progressive-audit/SKILL.md (PR #889) |
| Phase | 5 — 닫힌 오류 루프(closed error loop) audit (skill line 166-184) |
| Date | 2026-05-18 |
| Author | Hyunsoo Kim |
| Issue | #990 |
| Predecessor | Phase 4 audit (#962 / PR #963, merged e65f792) — STOP gate 영수증; gap fixes Step 1-3 (PR #965/#968/#987) merged |
| Successor | 없음 — 시퀀스 A closure. 5-phase progressive audit 완료. |
| Strict-forbid | 실제 taxonomy / dashboard / harden-process 구현 0건 (skill body 통일 — audit only) |
요약(Executive summary)
| # | item | 상태 | 핵심 evidence |
|---|---|---|---|
| 1 | Failure taxonomy 정의 (retrieval_miss / planner_under_decomposition / verifier_false_negative / verifier_false_positive / generator_hallucination / context_dilution / unknown 등) | ◐ partial | abstention_outcomes 3-bin (PR #464) 존재 — correct_refusal / incorrect_answer / boundary_partial (eval/run_eval.py:377-411). 그러나 7-category taxonomy 부재 — grep -E 'retrieval_miss\|planner_under_decomposition\|generator_hallucination\|context_dilution' --include='*.py' 0건. 3-bin 은 refusal-axis 만 분류; 정답 실패 (incorrect_answer) 의 root-cause stage 추적 부재. |
| 2 | Failure distribution dashboard (per-category 분포 표 / bar chart) | ◐ partial | 3-bin counts 노출 ✓ — reports/real100/baseline.aggregate.json 의 abstention_outcomes: {correct_refusal: 16, incorrect_answer: 87, boundary_partial: 0} 와 같이 raw count 가 aggregate 에 있음. per-stage failure distribution dashboard ✗ — scripts/ 의 어떤 plotter 도 stage-별 failure breakdown 그래프 emit 안 함 (plot_cost_frontier.py / plot_pareto.py 는 cost-accuracy 만, EDA 류는 corpus shape 만). |
| 3 | Monotone-harden process (새 failure mode → 카테고리 추가 + ≥5 예제 eval set 추가 워크플로) | ✗ absent | grep -E 'harden\|failure_mode\|new_category' --include='*.py' --include='*.md' docs/operations/ → 0건. docs/operations/ 에 incident-driven category 확장 protocol 부재. scripts/generate_real_cases.py (PR #936 ADR 0052) 는 hardcase generator 지만 failure-mode-driven 워크플로 (e.g. “이번 분기 incorrect_answer 87 케이스 분석 → 신규 category X 발견 → eval set 에 5 예제 추가”) 와 연결 안 됨. |
판정: partial 2 + absent 1. 가장 큰 갭 = item 3 (monotone-harden process 부재) — 본 audit 가 발견한 신규 failure mode (item 4 아래) 같은 신호가 들어와도 workflow 가 없어서 누적되지 않음.
본 audit 가 raw 측정에서 발견한 신규 failure mode
스킬 본문 (line 175-176) 의 추가 요구 — “audit 중 실제로 새 failure mode 1개 이상 raw 측정에서 발견 보고”.
Finding #1 — verifier_false_negative_on_unanswerable 대량 발생
Raw 측정 source: reports/real100/baseline.aggregate.json (post-PR #987, 8307819f).
"abstention_outcomes": {
"correct_refusal": 16,
"incorrect_answer": 87,
"boundary_partial": 0
},
"num_predictions": 221
해석:
- 본 corpus 의 unanswerable subset (no_answer hardcase) 에서 시스템이 87 case 답변 시도, 16 case 만 정확히 거부 → incorrect_answer / (correct_refusal + incorrect_answer) = 87/103 ≈ 84%.
boundary_partial = 0도 두 번째 신호 — boundary 케이스가 정확히 0건이라는 건 verifier 가 binary 로만 reject/accept 하고 partial 정보를 보존 안 함 을 시사. ADR 0003 (answer dictstatus: partial) 가 정의된 케이스가 실제 시스템 동작에서 emit 안 됨.
Failure mode 정의:
- 이름:
verifier_false_negative_on_unanswerable - 분류: verifier_false_negative 의 sub-category — 답이 없음을 모르고 답을 만들어내는 케이스.
- Root cause 가설 (확정 아님, 본 audit scope 외):
- verifier (
rag_verifier.py) 의 sufficiency rule 이 evidence 의 부재 보다 존재 에 weight 가 강함 — partial evidence 가 retrieved 되면 strict reject 안 함. - abstention 의
no_answerhardcase 가 generator 의 evidence-on-topic-but-no-answer 인식을 강제하지 않음.
- verifier (
왜 신규인가:
- ADR 0054 (PR #959 scorer fix) 가 측정 layer 에서 vacuous-truth 1.0 을 제거했지만, 시스템 동작 자체의 84% incorrect_answer rate 는 ADR 0054 이전에도 있었던 시스템 layer failure mode 다. ADR 0054 가 측정 정직성을 회복했기에 이제 보이는 failure mode.
- Phase 3 audit (#961) 의 4-item / Phase 4 audit (#963) 의 3-item 중 어디에서도 이 mode 가 명시 진단되지 않았음 — 본 audit 가 trace v2 (PR #968) + rationality_judge (PR #987) 누적 후 처음 표면화.
Finding #2 — abstention slice 의 planner 약점 (보조 신호)
Raw 측정 source: reports/real100/rationality.md (PR #987 산출).
### `planner_decomposition` — bottom 3
- real_hanyeong_noanswer_트랙시스템_예산규모 (slice=abstention) = 0.001
- real_광주연구원_no_answer_penalty_rate (slice=abstention) = 0.003
- real_BIFF_penalty_clause_no_answer (slice=abstention) = 0.005
bottom-3 planner_decomposition 모두 abstention slice. 단 — 현재 측정은 stub backend (SHA-256 해시) 이므로 signal 강도 약함 (uniform 분포). LLM backend regen (별 PR scope) 이후 진짜 신호 여부 재확인 필요. 보조 신호 로만 분류.
상세 진단
Item 1 — Failure taxonomy 정의
스킬 요구 (line 168-170): 시스템이 실패할 때 어느 단계에서 실패했는지 분류 가능한 taxonomy 존재 — retrieval_miss / planner_under_decomposition / verifier_false_negative / verifier_false_positive / generator_hallucination / context_dilution / unknown 등.
현재 wiring:
- abstention_outcomes 3-bin (PR #464):
eval/run_eval.py:377-411의compute_abstention_outcome()—correct_refusal/incorrect_answer/boundary_partial. 단 refusal-axis 만 분류 (answerable vs abstained 대각선의 4 quadrant 중 abstained 행). - answerable=True 의 실패 분류 부재: answerable case 가 fail 했을 때 root-cause stage (retrieval / verifier / generator) 추적 0건.
- trace v2 (PR #968, ADR 0001 invariance 유지) 가 raw 자료 (planner attempts / synthesis prompt+completion) 는 dump 하지만, category 라벨 은 emit 안 함.
갭(Gap):
- 7-category 분류기 (e.g.
eval/scorers/failure_classifier.py:classify(case, trace, expected) -> str) 부재. - 분류 없이는 distribution dashboard (item 2) / harden process (item 3) 모두 의미 불충분.
Supply 제안 (별 PR):
- 신규
eval/scorers/failure_classifier.py(~150 LOC) —classify_failure(case_result: dict) -> strreturning one of 7 categories. - Rule-based 분류 (LLM-judge 와 별도, deterministic):
- retrieval_miss:
case["evidence"] == []또는 expected doc not in retrieved - planner_under_decomposition: query_type=comparison/multi_hop 인데
trace["planner"]["attempts"]≤ 1 - verifier_false_negative: answerable=False AND status=supported (= 본 audit 의 87/103 패턴)
- verifier_false_positive: answerable=True AND status=insufficient AND expected_terms 가 evidence 에 존재
- generator_hallucination: claim 의 citation 이 evidence 에 부재
- context_dilution: top_k 가 default 보다 크고 expected doc 가 lower rank (k>4 의 케이스)
- unknown: 위 6 조건 미해당
- retrieval_miss:
- 출력:
case_results[*].failure_category: str신규 필드 (schema_version bump 검토). - 추정 ~150 LOC + 1 test (각 카테고리 1 fixture).
- ADR 후보: 신규 측정 표면 도입 — ADR 0057 (가칭) 가 contract 고정 필요할 가능성.
Item 2 — Failure distribution dashboard
스킬 요구 (line 171-172): 카테고리 별 실패 비율을 보여주는 dashboard / aggregate report.
현재 wiring:
- 3-bin counts 노출 ✓:
reports/real100/baseline.aggregate.json::abstention_outcomes— raw counts emit. - Markdown 표 부재:
reports/real100/어디에도 abstention_outcomes 의 percentage 표 render 안 됨.reports/real100/eda.md는 corpus shape 만,distinguishing_power.md는 gauge 만,rag_pipeline.md는 retry/cost 만. - per-category bar chart 부재:
scripts/plot_*.py중 failure-category-axis plotter 0건.
갭(Gap):
- Item 1 의 7-category 분류기 도입 이후 부산물로 자동 emit 되어야 할 distribution table 부재.
- 현 3-bin (refusal-axis) 만으로도 표 + Markdown render 가능한데 안 됨.
Supply 제안 (별 PR — item 1 이후):
scripts/render_failure_distribution.py(~80 LOC) —eval_summary.json읽고reports/real100/failure_distribution.{md,aggregate.json}emit.- 산출 schema:
{category_counts: {retrieval_miss: int, ..., unknown: int}, total: int, percentages: {...}}. - Markdown 은 단일 표 + 본 audit 의 finding #1 같은 highlight section.
.gitignoreallowlist 패치 (기존distinguishing_power.{md,aggregate.json}패턴 동일).- ADR 미발행 (read-only 산출물, 측정 표면 추가).
- 추정 ~80 LOC + 1 test.
Item 3 — Monotone-harden process
스킬 요구 (line 173-176): 신규 failure mode 가 발견될 때마다 (a) 카테고리 추가, (b) ≥5 예제를 eval set 에 추가, (c) regression test 가 그 category 의 fail rate 가 임계값 이하로 유지되는지 강제하는 워크플로 존재.
현재 wiring:
- (a) 카테고리 추가 워크플로 부재: item 1 supply 자체가 안 들어왔으므로 카테고리 자체가 부재.
- (b) hardcase generator 존재 ✓:
scripts/generate_real_cases.py(PR #936, ADR 0052) — 5 enum (distractor_heavy / ambiguous_query / multi_hop / no_answer / long_context) 기반 LLM-assisted generator. 단 enum 은 query-shape axis (어떤 모양의 query?), failure-mode axis (왜 fail?) 아님. cross-mapping 부재. - (c) regression test 부재:
tests/test_*_regression.py가 specific bug fix 의 lock-in 은 보호 (e.g.test_retrieval_loop_regression.py) 하지만 category fail rate ceiling 같은 aggregate invariant 는 lock-in 안 됨.
갭(Gap):
- 본 audit 의 finding #1 (87/103 incorrect_answer) 가 들어와도 어디에도 누적 안 됨 — 다음 분기에 같은 신호가 나와도 다시 raw inspection 으로 발견해야 함.
- ADR 0055 (claim_validator) 가 improvement claim 의 자동 검증을 도입했듯, 본 item 의 supply 는 failure regression 의 자동 차단을 도입할 수 있음.
Supply 제안 (별 PR — item 1 + item 2 이후):
- 신규
docs/operations/failure-mode-harden-process.md(~100 LOC) — 워크플로 정의:- raw incident → trace inspection → 신규 category 후보 등록
- category 별 ≥5 예제를
eval/real_config.local.yaml(or 합성eval/config.yaml) 에 hardcase 로 추가 eval/run_eval.py가 category 별 fail rate emittests/test_failure_rate_regression.py가 category 별 ceiling 보호 (e.g.verifier_false_negative_on_unanswerable ≤ 0.30)
- ADR 후보: ADR 0058 (가칭) — failure regression contract.
- 추정 ~100 LOC docs + ~50 LOC regression test + 1 ADR.
시퀀스 A 5-phase closure 영수증
| Phase | Audit PR | Gap fix PR | Closure |
|---|---|---|---|
| 1 (component-level isolation) | (skill 본문 Phase 1) | — | (본 시퀀스 외) |
| 2 (oracle ceilings) | (skill 본문 Phase 2) | — | (본 시퀀스 외) |
| 3 (프로세스 + trajectory) | #961 c50a3e7 |
item 2 → #968 9cb6c00 (trace v2) / item 3 → #987 8307819f (rationality_judge ADR 0056) |
✓ |
| 4 (statistical rigor) | #963 e65f792 |
item 3 → #965 7370fa0 (claim_validator ADR 0055) |
✓ |
| 5 (closed error loop, 본 audit) | #990 / 본 PR | — (skill 상 audit only) | ◐ 진단 완료, supply 별 PR |
시퀀스 A 의 5-step portfolio narrative:
- 측정 표면 도입 (PR #946 distinguishing-power gauge)
- Goodhart 함정 발견 + scorer semantics fix (PR #959 ADR 0054)
- 측정 표면 audit (PR #961 + #963 Phase 3/4 audit)
- 자동 게이트 도입 (PR #965 ADR 0055 claim_validator + PR #968 trace v2)
- Process rationality 측정 표면 도입 (PR #987 ADR 0056 rationality_judge)
- Closed error loop audit (본 PR) — 5-phase progressive audit 완주
범위 밖(Out-of-scope) (별 PR / 별 plan turn)
- Item 1 supply (failure_classifier.py + ADR 0057) — 별 PR scope.
- Item 2 supply (failure_distribution.{md,aggregate.json} + plot) — 별 PR scope.
- Item 3 supply (failure-mode-harden-process docs + regression test + ADR 0058) — 별 PR scope.
- LLM backend 로 rationality_judge regen (n=221 × 1 LLM call) — Step 3 (ADR 0056) Out-of-scope 와 동일.
- Phase 1/2 retro-fill audit (시퀀스 A 외, skill body 의 Phase 1/2 spec 그대로) — 별 시퀀스.
핵심 파일(Critical files) (read-only inspect 대상)
reports/real100/baseline.aggregate.json(post-PR #987 8307819f) — abstention_outcomes raw countsreports/real100/rationality.aggregate.json+rationality.md(PR #987 산출) — 축별 bottom-3eval/run_eval.py:377-411compute_abstention_outcome(PR #464 wiring)rag_verifier.py(rule-based, LLM call 0 — Step 2 audit finding)scripts/generate_real_cases.py(PR #936 ADR 0052) — hardcase generator (현 query-shape axis)docs/audits/eval-framework-phase3-audit.md(PR #961) — Phase 3 audit 포맷 referencedocs/audits/eval-framework-phase4-audit.md(PR #963) — Phase 4 audit 포맷 reference.claude/skills/eval-framework-progressive-audit/SKILL.md:166-184— Phase 5 spec
Verification
- audit doc 의 3-item 표 인용한 file path 모두 실제 존재 (
ls1회). - 본 audit 가 발견한 finding #1 의 raw count (87/103) 가
reports/real100/baseline.aggregate.json에 실제 존재 (python3 -c "import json; print(json.load(open('reports/real100/baseline.aggregate.json'))['abstention_outcomes'])"). - PR body §5b heading 존재 (CI lint gate).
gh pr checks <PR-N>11 gate 전부 green.