# Emergency Session — mdx 01~05 visible-result rescue **시작**: 2026-05-26 **목표 시간**: ~1 시간 **모드**: 로컬 코드 개선 (no Gitea push, no orchestrator, no issue 생성) --- ## 1. 목표 프론트에서 mdx 01~05 **5 개 모두 반드시 슬라이드 출력** (empty shell 금지) + 사용자가 frame 선택/변경 가능 + 선택된 frame 으로 AI 가 원문 보존하며 재배치. ### 정책 lock (한 줄) > **empty shell 금지. 어떤 경우든 원문 텍스트 보존 + 선택 가능 frame 또는 fallback frame 에 AI 가 재배치.** **현재 상태**: | mdx | 결과 | |---|---| | 03 | ✅ direct path — 정상 | | 04 | ❌ 04-1 reject → provisional, 04-2 plan 에서 누락. 1 slide 만 | | 05 | ❌ 05-1 + 05-2 empty_shell. 빈 placeholder | | 01, 02 | unknown (이번 세션 sanity check 필요) | **기대 (정책 적용 후)**: 모든 mdx 의 모든 section 이 슬라이드로 렌더 — reject-only 여도 AI 재배치 path 로 surface. --- ## 2. 운영 원칙 (lock) - ❌ git push 금지 - ❌ Gitea issue 생성 / orchestrator 실행 금지 - ❌ 결과물 하드코딩 금지 (mdx 별 특수 처리 X) - ❌ MDX 원문 텍스트 변경 금지 - ❌ 후보 수 강제 floor (3~6 padding) 금지 - ❌ **empty_shell 종결 금지** — 후보 reject-only 라도 AI adaptation route 필수 - ❌ **reject 후보 자동 렌더 금지** — 항상 ai_adaptation_required 로 status 부여 - ✅ 코드 path / 정책 / fallback 수정 - ✅ 각 edit 후 로컬 run 으로 검증 - ✅ Codex + 사용자 + Claude consensus 매 step --- ## 3. Root cause 진단 (확정) 3-layer contradiction: ``` Layer 1: vite.config.ts spawn env PHASE_Z_ALLOW_REJECT="1" ← 의도: reject 허용 ← 실제: src/ 에서 read 안 됨 (dead env var) Layer 2: src/phase_z2_pipeline.py:1481-1520 lookup_v4_non_reject_candidates() if j.get("label") == "reject": continue ← reject 강제 drop (May 8 lock) Layer 3: composition + IMP-30 u4 selection_path = "empty_shell" imp30_u4 = "terminal_first_render_empty_shell" ← AI fallback 도달 차단 ``` **결과**: reject 후보가 있어도 → filter 에서 drop → empty_shell → terminal → AI fallback 미호출. **Cross-reference**: - `.env: AI_FALLBACK_ENABLED=true` ✅ 활성 - 사용자 정책 ([feedback_ai_isolation_contract]): "reject = AI 재구성 대상 (frame 유지 + AI 재배치)" - IMP-47B (#76): reject 후보 surface = override path 만 (자동 path 미포함) ### Resolution — 통합 정책 3-layer contradiction 의 해소 = **§1 의 정책 lock ("empty shell 금지")** 적용 : 1. Layer 2 의 reject filter → **status 분류로 대체** (제거 X, 모두 보존 + status 부여) 2. Layer 3 의 empty_shell terminal → **제거** (어떤 후보 상태든 AI adaptation route 로 흐름) 3. Layer 1 의 dead env → wire-up 또는 retire (§4 Quick fix) ### P1 결과 (2026-05-26) — 3 종류 root cause 동시 존재 (단순 reject filter X) P1 검증으로 처음 가정 ("주로 reject filter 문제") 와 실제 가 다름 발견 : | 분류 | 영향 section | 처리 priority | |---|---|---| | **C1. All-reject (catalog gap)** | `02-2.1`, `04-1` (raw 32 모두 reject) | **P2** — reject = `ai_adaptation_required` (status 분류) | | **C2. Section key mismatch** | `02-2` ↔ `02-2.1/02-2.2`, `04-2` ↔ `04-2.1/04-2.2` (V4 source 가 split) | **P2.5** — parent ↔ child key alignment policy (universal) | | **C3. V4 source 자체 누락** | `mdx 05 전체` (entry 0) | **P3** — generic fallback frame mandatory (raw 후보 0 일반 정책) | **fixture 주의** : `tests/matching/v4_full32_result.yaml` = 2026-04-29 stale snapshot. 이번 세션 = fallback policy 강화로 fixture 의존 약화. runtime V4 evaluator migrate = 별 axis (이번 세션 후). ### Candidate status 3 분류 (정책 기반) | status | 후보 label | 처리 | |---|---|---| | `auto_renderable` | use_as_is / light_edit / restructure (일부) | 즉시 직접 렌더 가능 | | `ai_adaptation_required` | needs_adaptation / reject / fallback_candidate | 자동 렌더 X, AI 재배치 필요 | | `blocked_runtime` | visual_pending / catalog scaffolding 등 | 현재 시스템상 렌더 불가 (별 axis 로 추적) | **규칙**: - `auto_renderable` 1+ → 그 중 rank-1 으로 직접 렌더 - `auto_renderable` 0 + `ai_adaptation_required` 1+ → top 후보 AI adaptation route - 둘 다 0 → generic_fallback frame + AI adaptation (별 axis — 이번 세션 후순위, §4 결정 보류 참조) - **어떤 경우든 empty_shell 종결 X** --- ## 4. 작업 순서 (5 priority + 1 quick fix) ### Priority 1 — Raw 후보 분포 확인 (5~10분, read-only) **목적**: catalog gap (후보 없음) vs matching gap (후보 있지만 reject) 구분. **파일**: - `tests/matching/v4_full32_result.yaml` (raw 32 candidates source) - `data/runs/mdx04_clean_check_v2/phase_z2/steps/step05_v4_evidence.json` - `data/runs/mdx05_clean_check_v2/phase_z2/steps/step05_v4_evidence.json` **검증 질문**: - 04-1, 04-2, 05-1, 05-2 각각의 raw 32 candidates 중 reject 수 / non-reject 수? - 어떤 frame_template_id 들이 reject 됐나? (catalog 의 가능성) - candidate_status="no_non_reject_v4_candidate" 가 정말 모든 4 섹션? ### Priority 2 — V4 후보 보존 + status 분류 (15~20분, edit) **목적**: reject 후보 삭제 X, 모두 보존 + `candidate_status` 부여 (auto_renderable / ai_adaptation_required / blocked_runtime). **파일 + 함수**: - `src/phase_z2_pipeline.py:1481-1520` `lookup_v4_non_reject_candidates` - rename → `lookup_v4_candidates(v4, section_id, max_n=6, include_reject=True, alias_keys=None)` - **default = `include_reject=True`** (정책 lock 반영) - 모든 후보 반환 + 각 V4Match 에 `candidate_status` field 추가 - reject filter 제거 — visual_pending 만 `blocked_runtime` 로 표시 (drop X) **V4Match dataclass / dict 확장**: - `candidate_status: str` field 추가 - 분류 규칙 (단일 source of truth): - label ∈ {use_as_is, light_edit} → `auto_renderable` - label = restructure → `auto_renderable` (단, capacity_fit 통과 시) / `ai_adaptation_required` (mismatch 시) - label ∈ {needs_adaptation, reject, fallback_candidate} → `ai_adaptation_required` - visual_pending = True → `blocked_runtime` **호출처 검색 필요**: - `grep -n lookup_v4_non_reject_candidates src/` - 호출처 마다 status field 사용 여부 확인 + 필요 시 분기 추가 **composition 변경**: - `src/phase_z2_composition.py` (line ~681 부근) - 후보 retrieve → status 별 분류: - `auto_renderable` 1+ → rank-1 으로 직접 렌더 - 그 외 → `ai_adaptation_required` route (P3 의 empty_shell 우회 와 연결) ### Priority 2.5 — Section key alignment policy (10~15분, edit) [NEW — C2 처리] **목적**: pipeline normalize 의 section_id (예: `04-2`) 와 V4 source 의 split child keys (예: `04-2.1`, `04-2.2`) 매칭. **universal policy** — 02-2 / 04-2 모두 cover, mdx 별 dispatch X. **접근 — "child → parent pool merge" (Codex 안)** : parent 를 child 로 split 하는 방식 (composition planner 변경) 보다 빠르고 안전 : ``` section_id = X (e.g., "04-2") lookup_v4_candidates(X) → V4 에 X 직접 매칭 시도 hit → return as-is miss → fallback: child keys 검색 (X.1, X.2, X.3, ...) hit → 모든 child candidates 를 X 의 candidate pool 로 merge (origin: child_id 보존 — debug) miss → return [] (P3 의 generic fallback 으로 흐름) ``` **파일**: - `src/phase_z2_pipeline.py:1505` `_resolve_v4_section_key` — alias 기능 이미 일부 있음 (alias_keys param). 확장 필요. - `lookup_v4_candidates` (P2 에서 신설) — alias miss 시 child key scan 추가 **검증 sample**: - `04-2` lookup → V4 miss → `04-2.1` + `04-2.2` 찾음 → 5 candidates pool 로 merge (04-2.1=2, 04-2.2=3) - `02-2` lookup → V4 miss → `02-2.1` + `02-2.2` 찾음 → 5 candidates pool ### Priority 3 — empty_shell terminal 제거 + AI adaptation + generic fallback mandatory (15~20분, edit) **목적**: `terminal_first_render_empty_shell` (IMP-30 u4) **제거** — 어떤 후보 상태든 AI adaptation 으로 분기. **empty_shell 종결 = 0 보장**. mdx 05 같은 raw 후보 0 케이스도 cover (C3). **파일**: - `src/phase_z2_composition.py` 또는 `src/phase_z2_pipeline.py` 의 IMP-30 u4 분기점 - 검색: `terminal_first_render_empty_shell`, `empty_shell`, `__empty__` literal **정책 결정 (lock — universal, mdx 별 X)**: | condition | route | |---|---| | `auto_renderable` 1+ | rank-1 으로 직접 렌더 | | `auto_renderable` 0 + `ai_adaptation_required` 1+ | top 후보 AI adaptation route. selection_path = `"ai_adaptation_required"`, frame_template_id = 실제 frame | | `auto_renderable` 0 + `ai_adaptation_required` 0 (raw 후보 0 — C3) | **generic_fallback frame + AI adaptation** (mandatory, no longer deferred) | **generic_fallback frame 정책**: - catalog 의 가장 universal frame 1 개 지정 (예: simple bullet + title 형) - **신설 frame X — 기존 catalog 활용** - hardcode mdx 별 dispatch X — "raw 후보 0 일 때" 통일 트리거 - selection_path = `"generic_fallback"`, candidate_status = `"ai_adaptation_required"` (AI 가 원문 재배치) - catalog_gap metric 기록 (운영 시그널 — terminal X) **무엇이 더 이상 존재하지 않는가**: - `merge_type: "empty_shell"` - `selection_path: "empty_shell"` - `frame_template_id: "__empty__"` - `imp30_u4: "terminal_first_render_empty_shell"` (이게 terminal 이라는 의미) **기록은 유지** (운영 시그널): - `candidate_status_summary` 같은 metric 으로 catalog_gap 비율 추적 — 단 terminal 아님 ### Priority 4 — AI fallback 호출 trace (5~10분, read + verify) **목적**: .env 가 `AI_FALLBACK_ENABLED=true` 인데 실제 호출되는지 확인. empty_shell 전에 끊기는지. **파일**: - `src/phase_z2_ai_fallback/router.py:67` (`if not settings.ai_fallback_enabled:` gate) - `src/phase_z2_pipeline.py:4914` (`if not _settings.ai_fallback_enabled:`) **검증**: - mdx 04/05 로컬 run 후 `step12_ai_repair.json` 확인 — AI 호출 attempt 있나? - 없으면 Priority 3 fix 후 다시 확인 ### Priority 5 — Frontend candidate panel 검증 (5~10분, read + edit) **목적**: backend 후보 살아도 frontend 숨기면 사용자 못 씀. reject / restructure / needs_adaptation 후보가 우측 panel 에 나와야. **파일**: - `Front/client/src/components/FramePanel.tsx` (candidate rendering 로직) - `Front/client/src/components/SlideCanvas.tsx` (candidate selection 흐름) - API: `/api/run` response 의 candidate 필드 확인 **검증**: - mdx 04/05 로컬 run 결과 → frontend 가 reject 후보를 표시 / 선택 가능한지 ### Quick fix (5분) — PHASE_Z_ALLOW_REJECT wire-up 또는 retire **파일**: - `Front/vite.config.ts:903-904` — `PHASE_Z_ALLOW_REJECT: "1"` / `PHASE_Z_ALLOW_RESTRUCTURE: "1"` - `src/` — 현재 read 0 매치 **결정 안**: - **A. Wire-up**: `lookup_v4_candidates` 의 `include_reject` 가 이 env 도 source 로 read - **B. Retire**: vite spawn env 에서 제거 (dead var 제거) 선택은 작업 진행 중 결정. --- ## 5. 검증 — Acceptance criteria ### 각 priority 완료 후 sanity | priority | local run | 기대 | |---|---|---| | 1 | (read-only) | ✅ done — 04/05 raw 후보 분포 + C1/C2/C3 root cause 식별 | | 2 | mdx04/05 local pipeline | step05_v4_evidence 의 각 candidate 에 `candidate_status` field 존재 (auto / ai_adaptation / blocked), reject 후보 surface — **C1 처리** | | 2.5 | mdx02/04 local pipeline | `04-2` lookup → child `04-2.1` + `04-2.2` 후보 5 개 merge 됨 (debug log 에 origin child_id 보존), `02-2` 도 동일 — **C2 처리** | | 3 | mdx04/05 local pipeline | step06_composition_plan 의 `selection_path` ∈ {`auto_renderable_rank_1`, `ai_adaptation_required`, `generic_fallback`}, **`"empty_shell"` 0 회**, `frame_template_id != "__empty__"`, mdx 05 도 generic_fallback 으로 슬라이드 — **C1/C3 처리** | | 4 | mdx04/05 local pipeline | step12_ai_repair.json 에 AI 호출 trace 존재 (ai_adaptation route 도달) | | 5 | frontend 새로고침 | mdx 04/05 의 우측 panel 에 후보 surface + status 별 시각 구분 (예: 배지) | ### 최종 demo readiness - [ ] mdx 01: 의미 있는 슬라이드 나옴 - [ ] mdx 02: 의미 있는 슬라이드 나옴 (02-2 = C2 alignment 검증) - [ ] mdx 03: 정상 (회귀 X 검증) - [ ] mdx 04: 04-1 (C1 reject→AI), 04-2 (C2 alignment) 모두 슬라이드 나옴 - [ ] mdx 05: 05-1 + 05-2 generic_fallback 으로 슬라이드 나옴 (C3 처리) - [ ] **5 mdx 통합: empty_shell 결과 = 0** (어떤 mdx 도 empty 안 됨 — 정책 lock 검증) - [ ] **C2 검증**: 04-2 가 04-2.1/04-2.2 child candidates 받아옴 (parent ↔ child alignment universal) - [ ] **C3 검증**: mdx 05 가 V4 source 없음에도 generic_fallback frame + AI adaptation 으로 슬라이드 (raw 후보 0 일반 정책) - [ ] 각 슬라이드 frame 변경 가능 (panel UI, status 분류 surface) - [ ] AI 적용/재배치 동작 (mdx 텍스트 보존) - [ ] reject-only mdx (mdx 04-1, mdx 02-2.1) 가 AI adaptation 으로 슬라이드 생성 — 정책 lock end-to-end 검증 --- ## 6. 수정 대상 파일 — 최종 inventory **Edit 대상**: - `src/phase_z2_pipeline.py` — lookup_v4 함수 + Step 5/9 caller + empty_shell 분기 - `src/phase_z2_composition.py` — empty_shell terminal disposition 우회 + reject 후보 routing - (조건부) `src/phase_z2_ai_fallback/router.py` — gate 확인 후 필요 시 - (조건부) `Front/vite.config.ts` — `PHASE_Z_ALLOW_REJECT` wire-up 또는 retire - (조건부) `Front/client/src/components/FramePanel.tsx` — reject 후보 surface 검증/추가 **Read-only 진단**: - `tests/matching/v4_full32_result.yaml` - `data/runs/mdx04_clean_check_v2/phase_z2/steps/step05_v4_evidence.json` - `data/runs/mdx04_clean_check_v2/phase_z2/steps/step06_composition_plan.json` - `data/runs/mdx05_clean_check_v2/phase_z2/steps/step05_v4_evidence.json` - `data/runs/mdx05_clean_check_v2/phase_z2/steps/step06_composition_plan.json` - `data/runs/mdx05_clean_check_v2/phase_z2/steps/step12_ai_repair.json` - (priority 4 진행 후) 새 run 의 동일 step files **MDX 원문 (절대 변경 X)**: - `samples/mdx_batch/01.mdx` ~ `05.mdx` --- ## 7. 진행 추적 각 priority 완료 후 이 섹션에 결과 한 줄 추가 — 작업 끝나면 정리해서 후속 Gitea issue / commit 본문 reference. - [x] P1: Raw 후보 분포 확인 — C1 (02-2.1, 04-1 all-reject) / C2 (04-2 ↔ 04-2.1/04-2.2 split) / C3 (mdx 05 source 누락) 식별 - [ ] P2: V4 후보 보존 + 3-status 분류 (C1 처리 — reject = ai_adaptation_required) - [ ] P2.5: Section key alignment policy (C2 처리 — child → parent pool merge, universal) - [ ] P3: empty_shell terminal 제거 + AI adaptation + generic_fallback frame mandatory (C1/C3 처리) - [ ] P4: AI fallback 호출 trace - [ ] P5: Frontend candidate panel 검증 (status surface) - [ ] Quick fix: PHASE_Z_ALLOW_REJECT wire-up 또는 retire --- ## 8. 후속 정리 (이 세션 후) 작업 안정화 되면 별도 task 로: - 새 Gitea issue 생성 (P1~P5 결과 evidence 기반) - 분리된 commits 으로 push - 23 suspect pool deep-dive (별 axis) - queue tracker #97 update 이 단계는 **이번 세션 scope 외**. 데모 우선.