- emergency.md: P2/P2.5/P3/P5 = 구현·작동 실측 확인 표기, P4 = envelope 수정(9717898) 기록
- vite.config.ts: PHASE_Z_ALLOW_REJECT / PHASE_Z_ALLOW_RESTRUCTURE retire
(src read 0곳 — Emergency P2 가 reject 보존을 기본 정책화하여 불필요, §4 Quick fix 결정 B)
- 검증: tsc --noEmit 0 에러
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
16 KiB
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 금지") 적용 :
- Layer 2 의 reject filter → status 분류로 대체 (제거 X, 모두 보존 + status 부여)
- Layer 3 의 empty_shell terminal → 제거 (어떤 후보 상태든 AI adaptation route 로 흐름)
- 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_renderable1+ → 그 중 rank-1 으로 직접 렌더auto_renderable0 +ai_adaptation_required1+ → 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.jsondata/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-1520lookup_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_statusfield 추가 - reject filter 제거 — visual_pending 만
blocked_runtime로 표시 (drop X)
- rename →
V4Match dataclass / dict 확장:
candidate_status: strfield 추가- 분류 규칙 (단일 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
- label ∈ {use_as_is, light_edit} →
호출처 검색 필요:
grep -n lookup_v4_non_reject_candidates src/- 호출처 마다 status field 사용 여부 확인 + 필요 시 분기 추가
composition 변경:
src/phase_z2_composition.py(line ~681 부근)- 후보 retrieve → status 별 분류:
auto_renderable1+ → rank-1 으로 직접 렌더- 그 외 →
ai_adaptation_requiredroute (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-2lookup → V4 miss →04-2.1+04-2.2찾음 → 5 candidates pool 로 merge (04-2.1=2, 04-2.2=3)02-2lookup → 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/runresponse 의 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_REJECTwire-up 또는 retire - (조건부)
Front/client/src/components/FramePanel.tsx— reject 후보 surface 검증/추가
Read-only 진단:
tests/matching/v4_full32_result.yamldata/runs/mdx04_clean_check_v2/phase_z2/steps/step05_v4_evidence.jsondata/runs/mdx04_clean_check_v2/phase_z2/steps/step06_composition_plan.jsondata/runs/mdx05_clean_check_v2/phase_z2/steps/step05_v4_evidence.jsondata/runs/mdx05_clean_check_v2/phase_z2/steps/step06_composition_plan.jsondata/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.
- 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) — 2026-07-02 실측 검증: mdx04 04-1 reject 후보
ai_adaptation_required로 보존 (GitHub issue #9) - P2.5: Section key alignment policy (C2 처리 — child → parent pool merge, universal) — 2026-07-02 실측: 02-2 가 parent merge 로 frame 14 획득
- P3: empty_shell terminal 제거 + AI adaptation + generic_fallback frame mandatory (C1/C3 처리) — 2026-07-02 실측: mdx05 GENERIC_FALLBACK 렌더, 5/5 mdx empty shell 0
- P4: AI fallback 호출 trace — 2026-07-02: 호출은 됐으나 proposal envelope 불일치로 전량 ValidationError 유실 발견 → 수정 (커밋
9717898: 프롬프트 envelope 명시 + client 코어싱). 잔여: design_adaptation_plan apply 계층 미구현 (unsupported_kind — issue #7 axis) - P5: Frontend candidate panel 검증 (status surface) — FramePanel candidate_status 툴팁 + route 배지 구현 확인
- Quick fix: PHASE_Z_ALLOW_REJECT wire-up 또는 retire (미확인 — dead env var 여부 재점검 필요)
8. 후속 정리 (이 세션 후)
작업 안정화 되면 별도 task 로:
- 새 Gitea issue 생성 (P1~P5 결과 evidence 기반)
- 분리된 commits 으로 push
- 23 suspect pool deep-dive (별 axis)
- queue tracker #97 update
이 단계는 이번 세션 scope 외. 데모 우선.