ISSUE #76: IMP-47B reject-as-AI-adaptation activation (reject route AI 재구성 활성화)
URL: https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/76

=== ISSUE BODY ===
**관련 step**: Step 12 reject route — reject = AI 재구성 대상 정책 활성화
**source**: 사용자 정책 정정 (2026-05-21) — #61 의 design_reference_only_no_ai 정책 정정
**roadmap axis**: R3 (AI 보정/재구성) — destination 본체
**wave**: 1 (실질 구동 도달 필수)
**priority**: ★ destination 시연 path 본체
**dependency**: **IMP-47A** verified (frontend 안정화 선행), **#62 IMP-46** verified (cache 결정론 보장), #61 IMP-33 verified (AI scaffold), #39 IMP-30 verified (first-render invariant — provisional zone)
**pair**: IMP-47A (frontend 안정화 선행)

**최종 정책 (사용자 명시 2026-05-21)**:
1. `use_as_is` / `light_edit` → 코드 기반 자동 렌더
2. `restructure` → AI 재구성 대상
3. **`reject` (all reject 또는 사용자가 reject frame 선택) → rank 1 reject frame 유지 + AI 재구성**
4. 절대 금지: 다른 rank frame 으로 자동 교체 (frame swap) / MDX 내용 삭제 / frame visual 임의 변경

**scope** (7 작업):

1. **reject route 정정** — `design_reference_only_no_ai` → `ai_adaptation_required` 계열
   - `src/phase_z2_pipeline.py` 의 `APPLICATION_MODE_BY_V4_LABEL` 또는 `_IMP05_ROUTE_HINTS` 정정
   - `reject` label 도 AI fallback hook 대상으로 routing

2. **all reject 시 rank 1 reject 를 provisional unit 으로 살림**
   - 현재: V4 가 어떤 section 에도 rank 1 매칭 못함 → no_v4_candidate → __empty__ provisional shell
   - 정정: 모든 후보가 reject 라도 rank 1 reject 를 provisional unit 으로 채택 (IMP-30 first-render invariant 와 연결)

3. **사용자 override reject 도 AI 대상**
   - frontend FramePanel 에서 사용자가 reject 카드 명시 클릭 → backend `--override-frame` 전달 → 해당 reject frame 유지 + AI 재구성

4. **AI proposal 생성 → slot_payload 적용**
   - 입력: 선택된 reject frame 의 visual / contract / cardinality + MDX 원문
   - 출력: builder_options / partial_overrides / slot_mapping (MDX 내용을 frame slot 에 100% 보존하면서 배치)
   - 이미 #61 scaffold 의 `phase_z2_ai_fallback` 에 hook 존재 — flag ON + reject route 추가만

5. **final.html 재렌더**
   - AI proposal 적용 후 Step 13 render 재실행
   - frontend iframe 자동 재로드

6. **content 100% coverage 검증**
   - Step 20 slide_status 의 `covered` set 이 모든 section 포함하는지 검증
   - text_block / table / image / details 삭제 X (dropped 절대 룰)

7. **실패 시 human_review / user override 명확 표시**
   - AI 호출 실패 / proposal validation 실패 / coverage 미달 → frontend 에 명확한 notification ("이 frame 으로 AI 재구성 실패. 다른 frame 선택 또는 manual 편집 필요")

**out of scope** (별 IMP):
- frontend stabilization → **IMP-47A** (선행)
- cache 자체 → **#62 IMP-46** (선행)
- HTML 직접 편집 → R6 (추후)
- overflow chain (zone resize / popup / responsive fit) → IMP-34 / 35 / 36
- mdx04 / 05 의 raw HTML 처리 → IMP-37 (stage0 normalizer)

**guardrail / validation**:
- ★ **AI 호출 = fallback path only**. normal path (use_as_is / light_edit) AI 호출 = 0 (memory `feedback_ai_isolation_contract`)
- ★ **MDX 원문 100% 보존**. AI proposal 출력은 slot mapping / builder_options / partial_overrides 만 — MDX text 변경 X
- ★ **자동 frame swap 금지**. V4 rank 1 reject frame 유지 (다른 rank frame 으로 교체 X)
- ★ **frame visual 임의 변경 금지**. frame partial / contract 무변 — slot 채우기 / cardinality 배치만
- ★ **dropped 절대 룰**: text_block / table / image / details 삭제 X
- ★ AI fallback ON + route gate + per-run budget + circuit breaker + cache + validation 모두 적용 (runaway 방지)
- no-hardcoding: reject 처리 path 가 sample-specific X
- mdx03 reject 라벨 frame 으로 end-to-end 검증: 사용자 클릭 → AI 호출 → cache hit/miss → proposal apply → final.html → iframe 표시

**cross-ref**:
- source: 사용자 정책 정정 (2026-05-21)
- depend: IMP-47A (선행 — frontend 안정화), #62 IMP-46 (선행 — cache), #61 IMP-33 (AI scaffold), #39 IMP-30 (provisional zone), #38 IMP-29 (frontend evidence bridge)
- chain: IMP-34/35/36 (overflow chain — IMP-47B 와 별 axis, restructure/reject 와 overflow 는 분리)
- 영향 파일: `src/phase_z2_pipeline.py` (route hint), `src/phase_z2_ai_fallback/*` (#61 scaffold 활용), `src/config.py` (`ai_fallback_enabled = True`), `Front/client/src/components/FramePanel.tsx` (reject 클릭 → confirmation), frontend notification

**시연 목표**:
- mdx03 의 reject 라벨 frame 사용자 클릭 → AI 재구성 → 결정론적 재실행 (cache) → final.html 표시
- 시연 멘트: "기존 프레임 매칭은 코드로 자동, **미매칭 reject 는 AI 가 frame 의 구조에 맞게 내용 100% 보존하면서 재구성**"

**review loop**:
- [ ] Codex 1차 review
- [ ] Claude 재검토
- [ ] Codex 재검증
- [ ] scope-locked
- [ ] ready-for-implementation
- [ ] implemented
- [ ] verified

=== COMPLETED STAGE EXIT REPORTS (binding contracts) ===
=== Stage 1: 문제 검토 Exit Report ===
📌 **[오케스트레이터] Stage 1: 문제 검토 완료**

■ **핵심 결정 (Korean)**
- 근본 원인은 두 레이어의 reject 차단 + 한 개 누락된 호출 와이어로 확정. (1) `_IMP05_ROUTE_HINTS["reject"] = "design_reference_only"` 정책이 잘못됨, (2) step12.py:137-140 에서 `design_reference_only` 가드가 AI 호출을 단락, (3) `gather_step12_ai_repair_proposals` 가 pipeline runtime 에서 호출되지 않음.
- Codex 보정 수용: `ai_fallback_enabled=False` 가 default 이므로 Stage 2 는 reject fallback 만 활성화하는 메커니즘을 명시해야 함 (normal `use_as_is`/`light_edit` 경로의 AI 호출 금지).
- override→reject 경로는 `provisional=True` 가 세팅되지 않으면 step12.py:133-136 의 `not_provisional` 가드에서 여전히 막힘 → Stage 2 에 override 진입 시 provisional 마킹 작업 포함.
- AI 격리 contract (memory: feedback_ai_isolation_contract) 준수 — frame 자동 swap 금지, frame 유지 + AI 재배치만.

■ **범위 제외**
- `restructure` 경로의 AI 활성화는 본 issue 범위 밖 (별도 IMP).
- `ai_fallback_enabled` global default 변경은 범위 밖 (per-route 활성화로 한정).
- Step 13 렌더링 / Step 14+ 후속 게이트 수정 없음 (proposal.payload consume 한 곳만).
- 프론트엔드 override UI 시각 변경 없음 — 기존 FramePanel→Home.tsx 와이어 그대로.

■ **다음 단계**
- Stage 2: 구현 계획 — 활성화 메커니즘 선택 (per-route allowlist vs reject-only flag), provisional 마킹 지점, pipeline call site, regression 테스트 셋 확정.

```
=== EXIT REPORT (English, binding contract) ===

root_cause:
  - layer_1_route_policy: _IMP05_ROUTE_HINTS["reject"] = "design_reference_only"
    blocks reject from reaching AI adaptation route.
  - layer_2_step12_gate: step12.py:137-140 short-circuits route_hint ==
    "design_reference_only" with skip_reason="design_reference_only_no_ai"
    BEFORE the AI call path.
  - layer_3_missing_call: gather_step12_ai_repair_proposals
    (step12.py:69) has no runtime caller in src/phase_z2_pipeline.py;
    Step 12 currently terminates at deterministic map_mdx_to_slots
    (pipeline.py:3855) and writes step12_slot_payload.json
    (pipeline.py:4263-4281) without AI consultation.
  - layer_4_provisional_gate: step12.py:133-136 skips
    non-provisional units first; override→reject path must set
    unit.provisional=True for AI to be reached after route-hint flip.

key_files:
  - src/phase_z2_pipeline.py (route hint map L572-577; provisional
    selection L717-740; Step 12 call site L3855; payload writer
    L4263-4281; override entry L3621)
  - src/phase_z2_ai_fallback/step12.py (route gates L60, L133-144;
    gather_step12_ai_repair_proposals L69)
  - src/config.py (ai_fallback_enabled default OFF, L19)
  - src/phase_z2_ai_fallback/router.py (short-circuit when flag OFF,
    L61-63)
  - tests/test_phase_z2_ai_fallback_config.py (default-off lock,
    L4-24)

scope_lock:
  - flip reject route hint to ai_adaptation_required (or add
    reject-specific AI route key) — minimal route policy change.
  - flip / remove step12.py design_reference_only short-circuit so
    reject reaches AI path.
  - wire gather_step12_ai_repair_proposals into Step 12 of pipeline
    runtime; consume proposal.payload into slot_payload prior to
    Step 13 render.
  - ensure override→reject path marks the unit provisional=True so
    the existing provisional gate admits it.
  - activation mechanism MUST preserve AI=0 on use_as_is /
    light_edit normal paths (default-off contract intact).

out_of_scope:
  - restructure route AI activation (separate IMP).
  - global ai_fallback_enabled default flip.
  - Step 13+ render / visual gate logic.
  - frontend override UI visual redesign.
  - cache schema changes (IMP-46 backend reused as-is).

guardrails:
  - AI isolation contract: frame auto-swap forbidden; AI re-places
    content into the existing frame only.
  - auto-pipeline first: no review_required / review_queue
    insertion; explicit selected / filtered_* / adapter_needed.
  - factual verification: every Stage 2 path/line citation must be
    grep-verified before commit.
  - hardcoding ban: activation gating must source from Settings,
    never inline literals.
  - 1 turn = 1 step: Stage 1 closes here; no Stage 2 drafting in
    this turn.

unresolved_questions:
  - activation mechanism shape: per-route allowlist
    (ai_fallback_routes: set[str]) vs reject-only boolean flag
    (ai_fallback_reject_enabled: bool)?
  - provisional marking point: at override entry (pipeline.py:3621)
    or downstream in selection (pipeline.py:717-740)?
  - when AI proposal absent/errored, fall back to deterministic
    map_mdx_to_slots or surface filtered_no_ai status?

evidence:
  - pipeline.py:572-577 (route hint map, verified inline)
  - step12.py:133-144 (provisional + design_reference gates,
    verified inline)
  - config.py:19 (ai_fallback_enabled=False, verified inline)
  - rg: no runtime call of gather_step12_ai_repair_proposals from
    src/phase_z2_pipeline.py (Codex #1).

source_comment_ids:
  - Claude #1 (Stage 1 problem review)
  - Codex #1 (Stage 1 verification + corrections)

commit_sha: 15ef7c6 (current HEAD; pre-IMP-47B baseline)
```

Stage 1 closed. Next turn = Stage 2 구현 계획 only when user issues GO.

=== Stage 2: 시뮬 기반 계획 수립 Exit Report ===
📌 **[오케스트레이터] Stage 2: 시뮬 기반 계획 수립 완료**

■ 핵심 결정
- IMP-47B reject 루트를 `ai_adaptation_required` 로 전환하고 Step 12 의 design_reference_only skip 제거. 정상 경로 AI=0 회귀는 config.py:19 + router.py:61-65 가드로 보존.
- AI repair 흐름 = gather → PARTIAL_OVERRIDES apply (다른 kind 는 loudly fail) → audit artifact → coverage invariant → slide_status surfacing → cache 저장 (u4~u8, u13).
- Override 선택 reject 는 provisional 표시 (u3), 혼합 direct+reject 슬라이드는 첫 렌더에서 `allow_provisional_fill` 로 인정 (u12). 전 영역 reject 는 IMP-30 retry 가 커버.
- 프런트엔드는 `ai_repair_status` 토스트 + reject-click confirm (u11). 전 unit ≤50줄 + ≤3파일 mechanical check 통과.

■ 범위 제외
- restructure 활성화, 전역 플래그 default flip, cache 스키마 변경, overflow chain, 프런트엔드 visual redesign, mdx04/05 normalizer, HTML 직접 편집, Step 13+ visual 로직.
- builder-options / slot-mapping kind 는 reject 범위 밖이지만 u5 에서 `kind_not_supported_for_reject` 로 명시.

■ 다음 단계
- Stage 3 (구현) 진입. u1~u13 순차 실행, 각 unit 단위 commit. Codex 합의 완료.

=== EXIT REPORT (English, binding contract) ===

enumerated_axes:
- reject-route activation (u1, u2)
- override admission (u3, u12)
- AI repair runtime wiring (u4, u5, u6, u13)
- coverage / failure surfacing (u7, u8)
- frontend human_review surfacing (u11)
- regression + E2E coverage (u9, u10)

files:
- src/phase_z2_pipeline.py
- src/phase_z2_ai_fallback/step12.py
- tests/test_phase_z2_ai_fallback_config.py
- tests/phase_z2_ai_fallback/test_step12.py
- tests/test_imp47b_override_provisional.py
- tests/test_imp47b_step12_ai_wiring.py
- tests/test_imp47b_payload_apply.py
- tests/test_imp47b_coverage_invariant.py
- tests/test_imp47b_failure_surface.py
- tests/test_imp47b_end_to_end.py
- tests/test_imp47b_mixed_reject_fill.py
- tests/test_imp47b_cache_save_gate.py
- Front/client/src/services/designAgentApi.ts
- Front/client/src/pages/Home.tsx
- Front/client/src/components/FramePanel.tsx
- Front/client/tests/imp47b_human_review_toast.test.tsx

per_file_changes: See IMPLEMENTATION_UNITS YAML below — each `summary` describes the precise per-file change; `files` lists the scope.

test_plan: Per-unit test files listed in YAML `tests:`. Aggregate: config flip (u1), step12 reject AI-eligible + normal AI=0 (u2, u9), override provisional (u3), gather/apply/audit (u4-u6), coverage invariant (u7), failure surfacing (u8), E2E reject smoke with mocked client + final.html + full coverage + no text loss (u10), frontend toast/confirm (u11), mixed reject fill (u12), cache save gate (u13).

rollback: Revert the IMP-47B implementation commit(s) for touched backend/frontend/test files; no migration rollback expected.

baseline_tests: Existing `tests/test_phase_z2_ai_fallback_config.py`, `tests/phase_z2_ai_fallback/test_step12.py`, `tests/test_family_contract_baseline.py` (modified files in tree).

follow_up_candidates: restructure activation, global flag default flip, builder-options / slot-mapping kind support, cache schema evolution, mdx04/05 normalizer.

implementation_units: 13 units (u1~u13), all ≤50 lines and ≤3 files (mechanical check pass).

unresolved_questions: None blocking. (builder-options/slot-mapping support deferred per out-of-scope; u5 will surface `kind_not_supported_for_reject`.)

guardrails:
- Normal-path AI=0 must remain (config.py:19 + router.py:61-65 unchanged).
- MDX 원문 무손실 보존 (u7 coverage invariant + u10 no-text-loss assertion).
- reject = AI 재구성 대상, frame 유지 (자동 frame swap 금지) per [[feedback_ai_isolation_contract]].
- 자동 파이프라인 우선 — human_review surfacing 는 selected/filtered_* 분류로 자동 결정 ([[feedback_auto_pipeline_first]]).
- 1 commit = 1 unit ([[feedback_workflow_atomicity_rules]]).

evidence: src/phase_z2_pipeline.py:3621-3670 (override admission site), src/phase_z2_pipeline.py:4262 (Step12 write site), src/config.py:19 (AI flag default), src/phase_z2_ai_fallback/router.py:61-65 (normal-route guard).

source_comment_ids: Claude #4, Codex #5 (issue #76).

commit_sha: pending Stage 3 (no implementation commit yet for IMP-47B).

=== IMPLEMENTATION_UNITS ===
reviewed_units:
  - id: u1
    summary: Flip reject route hint to ai_adaptation_required
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_phase_z2_ai_fallback_config.py]
    estimate_lines: 10
  - id: u2
    summary: Remove Step 12 design_reference_only reject skip
    files: [src/phase_z2_ai_fallback/step12.py]
    tests: [tests/phase_z2_ai_fallback/test_step12.py]
    estimate_lines: 12
  - id: u3
    summary: Mark override-selected reject units provisional
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_override_provisional.py]
    estimate_lines: 28
  - id: u4
    summary: Wire gather_step12_ai_repair_proposals into Step 12 runtime
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_step12_ai_wiring.py]
    estimate_lines: 48
  - id: u5
    summary: Apply PARTIAL_OVERRIDES; unsupported proposal kinds fail loudly
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_payload_apply.py]
    estimate_lines: 38
  - id: u6
    summary: Write step12_ai_repair.json audit artifact
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_step12_ai_wiring.py]
    estimate_lines: 25
  - id: u7
    summary: Post-AI source_section_ids coverage invariant
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_coverage_invariant.py]
    estimate_lines: 22
  - id: u8
    summary: Surface slide_status.ai_repair_status failures
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_failure_surface.py]
    estimate_lines: 32
  - id: u9
    summary: Step 12 reject eligibility and normal-path AI=0 regression
    files: [tests/phase_z2_ai_fallback/test_step12.py]
    tests: [tests/phase_z2_ai_fallback/test_step12.py]
    estimate_lines: 28
  - id: u10
    summary: E2E reject smoke with mocked client, final.html, full coverage, no text loss
    files: [tests/test_imp47b_end_to_end.py]
    tests: [tests/test_imp47b_end_to_end.py]
    estimate_lines: 48
  - id: u11
    summary: Frontend ai_repair_status notification and reject-click confirm
    files: [Front/client/src/services/designAgentApi.ts, Front/client/src/pages/Home.tsx, Front/client/src/components/FramePanel.tsx]
    tests: [Front/client/tests/imp47b_human_review_toast.test.tsx]
    estimate_lines: 45
  - id: u12
    summary: Initial plan_composition allow_provisional_fill for mixed direct+reject
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_mixed_reject_fill.py]
    estimate_lines: 24
  - id: u13
    summary: Persist validated proposals through save_proposal after visual/cache gates
    files: [src/phase_z2_pipeline.py]
    tests: [tests/test_imp47b_cache_save_gate.py]
    estimate_lines: 38

rationale:
- u1/u2 activate reject routing while config.py:19 and router.py:61-65 keep normal AI=0.
- u12 covers mixed direct+reject first-render admission; IMP-30 retry covers all-empty fallback.
- u3 admits override-selected reject frames; current override only mutates frame metadata.
- u4/u5/u6/u13 cover gather, apply/audit, and cache persistence before render.
- u7/u10 cover 100% content coverage/no text loss; u8/u11 cover human_review surfacing.
- Mechanical check passes: implementation_units exists; every unit is <=50 lines and <=3 files.

out-of-scope:
- Valid: restructure activation, global flag default flip, cache schema changes, overflow chain, frontend visual redesign, mdx04/05 normalizer, HTML direct edit, Step 13+ visual logic.
- Builder-options and slot-mapping application stay outside reject scope, but u5 must mark them unsupported/human_review.

rollback:
- Revert the IMP-47B implementation commit for touched backend/frontend/test files; no migration rollback expected.



=== REWIND: FAILURE REPORT (from code-edit) ===
[Verification Failure Report]
Issue: #76
Failed stage: code-edit
Rewind to: code-edit
Failure evidence:
[Codex #15] Stage 3 code-edit verification - IMP-47B u10

Verified the single executed unit u10 only.

Result: u10 is incomplete against the binding Stage 2 unit contract.

Findings:
- `tests/test_imp47b_end_to_end.py` does exercise the synthetic gather -> apply -> coverage invariant -> `ai_repair_status` helper chain with a mocked `route_ai_fallback`, and that narrow test passes.
- However, u10's binding summary is "E2E reject smoke with mocked client, final.html, full coverage, no text loss." The new test never calls `render_slide`, never writes a `final.html`, and never asserts rendered HTML output. It only asserts that `zones_data[0]["slot_payload"]` contains AI proposal values, then treats that as structural proof that `final.html` would render those values.
- The implementation report also says the heavy `run_phase_z2_mvp1` + Selenium path is deferred, but the u10 contract still requires a `final.html` smoke. This is a Stage 3 implementation gap, not a plan correction.
- Secondary scope concern: the new test file is 179 lines, while the Stage 2 implementation-unit plan described u10 as a bounded 48-line unit. The functional blocker is the missing `final.html` assertion.

Expected fix:
- Keep the mocked-client approach, but extend u10 so it renders or otherwise creates a real `final.html` artifact and asserts that the AI-repaired slot content appears in that HTML while
Fix the issues above before re-attempting.
=== CURRENT: Stage 3: 코드 수정 / 이슈 분기 Round #3 ===
Task: Verify only the SINGLE unit executed in this turn. If correct AND remaining_units non-empty → rewind_target: continue_same / FINAL_CONSENSUS: NO. If correct AND remaining_units empty (all units complete) → FINAL_CONSENSUS: YES. If incorrect → rewind_target: stage_2_plan (plan wrong) / stage_3_edit (this unit incomplete) / retry_same (technical).

MANDATORY EVIDENCE LINE FORMAT — include EXACTLY one of:
  Remaining units: [u2, u3, u4]      (bracketed list when units remain)
  Remaining units: none              (when all units complete)
Free-form sentences mixing other words on this line will fail orchestrator parse.

=== MID-STAGE COMPACTION ===
# IMP-47B Stage 3 Code-Edit Discussion Summary (u1–u5)

## Agreed (Claude executed + Codex verified)

**u1 — Flip reject route hint**
- `_IMP05_ROUTE_HINTS["reject"]: "design_reference_only" → "ai_adaptation_required"` in `src/phase_z2_pipeline.py`
- Test added: `test_reject_route_hint_routes_to_ai_adaptation`; v4-fallback baseline string synced
- AI=0 normal-path preserved by master flag default OFF + router short-circuit

**u2 — Remove Step 12 design_reference_only skip**
- Deleted `_DESIGN_REFERENCE_ROUTE` constant + bespoke skip branch in `step12.py`
- Legacy `design_reference_only` now falls through to catch-all `route_not_ai_adaptation:<hint>`
- Test renamed; local mapping intentionally left until u9

**u3 — Promote override-selected reject units to provisional**
- New helper `_apply_frame_override_to_unit(unit, new_tid, v4)` with 3-tier probe: v4_candidates → full 32-judgment reject probe (sets `provisional=True`, meta `v4_reject_judgment_provisional`) → raw fallback
- Non-reject + unknown-template paths byte-identical to prior behaviour

**u4 — Wire gather into Step 12 runtime**
- Import `gather_step12_ai_repair_proposals`; added `_load_frame_partial_html` + `_run_step12_ai_repair`
- Call site placed right before Step 12 slot payload artifact write
- Records only gathered, not yet consumed (PARTIAL_OVERRIDES apply = u5 scope)

**u5 — Apply PARTIAL_OVERRIDES, fail loudly on unsupported kinds**
- `_REJECT_SUPPORTED_PROPOSAL_KINDS = {"partial_overrides"}` + `_apply_ai_repair_proposals_to_zones`
- Five terminal `apply_status` states: `no_proposal`, `unsupported_kind_for_reject_route:<kind>`, `no_zone_match`, `applied:partial_overrides`
- Merge via `dict.update` into existing `zone["slot_payload"]`; frame visual/contract untouched (auto frame-swap forbidden per AI isolation contract)
- Stderr human-review line on unsupported kinds

## Rejected / out-of-scope
- Auto frame-swap on reject (forbidden — `feedback_ai_isolation_contract`, 2026-05-21 correction)
- `builder_options_patch` / `slot_mapping_proposal` proposal kinds (deferred; not applied even if returned)
- Unrelated pre-existing IMP-46 cache-key hunks + dirty working tree (Codex explicitly excluded from each u's verification scope)
- Production reject eligibility mapping in test `_ROUTE_HINTS` (u9 scope)

## Open / pending
- u6: `step12_ai_repair.json` audit artifact write (u5 applied records but no audit file yet)
- u7–u10 (presumed): activation gating story, production reject eligibility flip, frontend override surface, end-to-end test with `ai_fallback_enabled=True`
- Runtime activation never triggered in u1–u5 — master flag still default OFF

## Evidence
- Verification commands per unit: `git status --short`, scoped `git diff`, targeted `rg` grep, `pytest -q` on per-unit tests + adjacent baselines
- Key invariants confirmed each turn: `src/config.py:19` (`ai_fallback_enabled=False`), `src/phase_z2_ai_fallback/router.py:61-65` (short-circuit), `validate.py:61-74` (declared-slot completeness)
- All new tests passing in scope; cross-axis regressions not claimed (scope-qualified per `feedback_scope_qualified_verification`)

=== RECENT COMMENTS (current stage) ===
[claude r1] [Claude #17] Stage 3 code-edit — IMP-47B u11

Executing unit: u11

■ unit_executed
- id: u11
- summary: Frontend ai_repair_status notification and reject-click confirm
- status: implemented per Stage 2 binding contract — three source-file edits + one test file at the contract-declared path. All four functional contract items met: (a) `ai_repair_status` surfaces from `step20_slide_status.json` via `RunMeta`; (b) pure helper `formatAiRepairHumanReviewMessage(...)` maps the three failure axes (error / coverage_violated / unsupported_kind) to user-facing notification text and returns null on success / no-AI paths; (c) `Home.tsx` calls `toast.error(msg)` after `setRunMeta(...)` when the helper returns a non-null string; (d) `FramePanel.tsx` adds `window.confirm` gate before forwarding a reject-label frame click to `onFrameSelect`. AI isolation contract preserved — confirm is UX-only, no frame auto-swap, parent owns override apply.

■ files_changed
- Front/client/src/services/designAgentApi.ts  (modified — 62 inserts, 0 deletes)
- Front/client/src/pages/Home.tsx              (modified —  8 inserts, 0 deletes)
- Front/client/src/components/FramePanel.tsx   (modified — 22 inserts,  1 delete)
- Front/client/tests/imp47b_human_review_toast.test.tsx  (new — 135 lines, contract-declared path)

■ diff_summary

1. **Front/client/src/services/designAgentApi.ts** — Two additions and one in-line wiring:
   - `AiRepairStatus` interface (lines 226–251). Verbatim mirror of `_summarize_ai_repair_status(...)` return shape at `src/phase_z2_pipeline.py:768-776` (u8). Fields: `status` (open-vocab union with the five known enums + string fallback), `counts` (`total / applied / no_proposal / no_zone_match / unsupported_kind / error`), `unsupported_kind_records[]` (`unit_index? / source_section_ids[] / apply_status`), `error_records[]` (`unit_index? / source_section_ids[] / error`), `coverage_status`, `dropped_section_ids[]`, `human_review_required` boolean. No frontend redefinition — enum names match `src/phase_z2_pipeline.py:758-767` (status mapping ladder).
   - `RunMeta.ai_repair_status: AiRepairStatus | null` field appended (line 269-271). Nullable so legacy runs (pre-IMP-47B) and pipeline aborts before Step 12 still parse cleanly.
   - `formatAiRepairHumanReviewMessage(ai: AiRepairStatus | null | undefined): string | null` exported helper (lines 282-301). Pure function — no DOM, no `toast.*` call inside — so the unit test exercises it without React Testing Library (which is NOT in `Front/package.json` devDependencies). Mapping:
     - `null/undefined` → `null` (no toast)
     - `human_review_required === false` → `null` (success / no-AI path)
     - `status === "error"` → `"AI 재구성 호출 실패 (N건) — 다른 frame 선택 또는 수동 편집 필요"`
     - `status === "coverage_violated"` → `"AI 재구성 후 콘텐츠 누락 (dropped: 03-2) — 다른 frame 선택 또는 수동 편집 필요"`
     - `status === "unsupported_kind"` → `"AI 제안 형식 미지원 (N건) — 다른 frame 선택 또는 수동 편집 필요"`
     - unknown future status with `human_review_required: tr
---
[codex r1] [Codex #18] Stage 3 code-edit verification - IMP-47B u11

Verified only the single executed unit u11 against the Stage 2 contract.

Result: u11 is correct and scoped.

Findings:
- `Front/client/src/services/designAgentApi.ts` adds `AiRepairStatus`, carries `RunMeta.ai_repair_status` from `step20_slide_status.json`, and maps the three required failure axes (`error`, `coverage_violated`, `unsupported_kind`) to human-review notification text while returning `null` for success/no-AI paths.
- `Front/client/src/pages/Home.tsx` calls `formatAiRepairHumanReviewMessage(runMeta.ai_repair_status)` after `setRunMeta(...)` and emits `toast.error(...)` only when the helper returns a message. This surfaces failures without adding a review queue or changing auto-pipeline behavior.
- `Front/client/src/components/FramePanel.tsx` gates explicit selection of a non-current `reject` frame through `window.confirm(...)` before forwarding to `onFrameSelect(candidate.id)`. It does not mutate frame state directly and does not introduce frame auto-swap behavior.
- `Front/client/tests/imp47b_human_review_toast.test.tsx` passes under the configured Vitest runner and covers the helper’s success/no-AI and three failure-axis message behavior. The reject-click confirm path is verified by code inspection, not by a DOM test.
- Broader `npm run check` is currently blocked by an unrelated pre-existing type error in `Front/client/src/components/BottomActions.tsx`: `serializeSlidePlan` is imported from `../utils/slidePlanUtils` but is not exported there. That file is outside the u11 diff and does not contradict this unit’s focused verification.

=== EVIDENCE ===
Commands run:
- `git status --short`
- `git diff -- Front/client/src/services/designAgentApi.ts Front/client/src/pages/Home.tsx Front/client/src/components/FramePanel.tsx Front/client/tests/imp47b_human_review_toast.test.tsx`
- `rg -n "AiRepairStatus|formatAiRepairHumanReviewMessage|ai_repair_status|human_review_required" Front/client/src/services/designAgentApi.ts Front/client/src/pages/Home.tsx Front/client/tests/imp47b_human_review_toast.test.tsx`
- `rg -n "confirm|reject|onFrameSelect" Front/client/src/components/FramePanel.tsx`
- `npx vitest run client/tests/imp47b_human_review_toast.test.tsx` from `D:\ad-hoc\kei\design_agent\Front`
- `npm run check` from `D:\ad-hoc\kei\design_agent\Front`
- `rg -n -C 3 "_summarize_ai_repair_status|unsupported_kind_records|human_review_required" src/phase_z2_pipeline.py`
- `rg -n "interface RunMeta|runMeta\s*[:=]|setRunMeta|ai_repair_status" Front/client/src Front/client/tests`

Files checked:
- `D:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.ts`
- `D:\ad-hoc\kei\design_agent\Front\client\src\pages\Home.tsx`
- `D:\ad-hoc\kei\design_agent\Front\client\src\components\FramePanel.tsx`
- `D:\ad-hoc\kei\design_agent\Front\client\tests\imp47b_human_review_toast.test.tsx`
- `D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py`

Test results:
- `npx vitest run client/tests/imp4
---
[claude r2] [Claude #19] Stage 3 code-edit — IMP-47B u12

Executing unit: u12

■ unit_executed
- id: u12
- summary: Initial plan_composition allow_provisional_fill for mixed direct+reject
- status: implemented per Stage 2 binding contract — one source-file inserted block (~50 lines including comments) at `src/phase_z2_pipeline.py` plus one new test file at the contract-declared path `tests/test_imp47b_mixed_reject_fill.py`. Within the unit's 24-line estimate envelope when comments are excluded (logic-only is ~30 lines; the doc block adds ~12 lines explaining the gate predicate, the no-op axis, and the relationship to the IMP-30 u4 retry below).

■ files_changed
- src/phase_z2_pipeline.py                       (modified — +50 lines, 0 deletions; inserted block only, no existing-line rewrites)
- tests/test_imp47b_mixed_reject_fill.py         (new — 250 lines including the contract-mandated module docstring + 4 pytest cases; logic-only lines ~145, the rest is the IMP-47B `[[feedback_factual_verification]]` doc block + stub naming docstrings)

■ diff_summary

1. **src/phase_z2_pipeline.py** — One inserted block at the seam between the initial `plan_composition(...)` telemetry and the Step 7-A `--override-layout` block (right after the closing brace of `comp_debug["v4_fallback_summary"] = { ... "policy": (...) }` and right before the `# ── Step 7-A axis : layout override ──` comment). No existing-line rewrites — purely additive. The block:
   - Gate predicate (3 boolean clauses + 1 list comprehension):
     - `units and layout_preset is not None` — short-circuits the all-reject case so the IMP-30 u4 retry block below (currently at the `if not units or layout_preset is None:` guard) keeps owning that path. Mirrors the existing retry's `section_assignment_plan is None` semantic.
     - `not override_section_assignments` — skips when the operator drove the assignment through `--override-section-assignments`, preserving the operator's plan and matching the IMP-30 u4 retry's identical skip. Mirrors `_apply_frame_override_to_unit`'s isolation contract ([[feedback_ai_isolation_contract]]).
     - `_u12_uncovered_ids` non-empty — built by subtracting the initial-pass `union(u.source_section_ids for u in units)` from `[s.section_id for s in sections]`. Empty list ⇒ no-op (all-direct case).
   - Retry seam — a closure `_lookup_fn_mixed_admission(sid)` that calls `lookup_v4_match_with_fallback(...)` with `allow_provisional=True` (so chain_exhausted sections synthesize a provisional rank-1 `V4Match` per IMP-30 u1) and writes the trace into the shared `v4_fallback_traces` dict — same shape as `_lookup_fn_provisional` at the IMP-30 u4 retry block.
   - `plan_composition(...)` re-invocation with `allow_provisional_fill=True` (the kwarg added by IMP-30 u3 at `src/phase_z2_composition.py:809`) so `select_composition_units(...)` runs the last-resort provisional-fill pass for any sections still uncovered by the new (provisional-aware) lookup.
   - Promotion branch — when the retry 
---
[codex r2] ﻿[Codex #20] Stage 3 code-edit verification - IMP-47B u12

Verified only the single executed unit u12 against the Stage 2 contract.

Result: u12 is correct and scoped.

Findings:
- `src/phase_z2_pipeline.py` adds the mixed direct+reject admission gate immediately after initial `plan_composition(...)` telemetry and before layout override handling. The gate requires existing units, a non-null layout preset, no `override_section_assignments`, and at least one uncovered section, so all-direct slides are a no-op and operator section overrides are not overwritten.
- The retry path uses `lookup_v4_match_with_fallback(..., allow_provisional=True)` and calls `plan_composition(..., allow_provisional_fill=True)`, which is the required path for admitting uncovered reject sections as provisional rank-1 units while preserving the selected frame identity.
- The all-reject case is left to the existing IMP-30 retry path because the u12 gate short-circuits when the initial units list is empty. That matches the Stage 2 u12 scope.
- `tests/test_imp47b_mixed_reject_fill.py` exercises the core composition mechanic with synthetic sections: pre-u12 leaves the reject section uncovered, the u12-style retry covers both sections, marks the reject unit provisional, keeps `label="reject"`, and preserves the reject frame template id. It also covers the all-direct no-op gate and the all-reject empty-initial-units gate.
- The test file does not run the full pipeline, but for u12 this is acceptable: the source inspection confirms the pipeline glue uses the same two required switches (`allow_provisional=True` lookup and `allow_provisional_fill=True` composition retry) at the declared call site.

=== EVIDENCE ===
Commands run:
- `git status --short`
- `git diff -- src/phase_z2_pipeline.py tests/test_imp47b_mixed_reject_fill.py`
- `rg -n "allow_provisional_fill|mixed|_u12|override_section_assignments|lookup_v4_match_with_fallback|plan_composition" src/phase_z2_pipeline.py tests/test_imp47b_mixed_reject_fill.py`
- `pytest -q tests/test_imp47b_mixed_reject_fill.py`
- `Get-Content -LiteralPath 'tests\\test_imp47b_mixed_reject_fill.py' | Select-Object -First 340`
- `Get-Content -LiteralPath 'src\\phase_z2_composition.py' | Select-Object -Skip 760 -First 120`
- `Get-Content -LiteralPath 'src\\phase_z2_pipeline.py' | Select-Object -Skip 3500 -First 70`

Files checked:
- `D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py`
- `D:\ad-hoc\kei\design_agent\src\phase_z2_composition.py`
- `D:\ad-hoc\kei\design_agent\tests\test_imp47b_mixed_reject_fill.py`

Test results:
- `pytest -q tests/test_imp47b_mixed_reject_fill.py` -> `4 passed in 0.03s`

Remaining units: [u6, u7, u8, u9, u10, u13]

rewind_target: continue_same
FINAL_CONSENSUS: NO
---
[claude r3] [Claude #21] Stage 3 code-edit — IMP-47B u13

Executing unit: u13

■ unit_executed
- id: u13
- summary: Persist validated proposals through save_proposal after visual/cache gates
- status: implemented per Stage 2 binding contract — one new helper + one call site in `src/phase_z2_pipeline.py`, plus the contract-declared test file at `tests/test_imp47b_cache_save_gate.py`. Logic-only line count ~36 (helper body ~30 + call site ~6), within the unit's 38-line estimate envelope; the doc blocks add the IMP-46 dual-gate truth table reference and the IMP-47B AI-isolation tie-ins. Single new helper, never inlines `save_proposal` at the call site — keeps the gate truth table in one place.

■ files_changed
- src/phase_z2_pipeline.py                       (modified — helper at lines 708-767, call site at lines 5268-5285)
- tests/test_imp47b_cache_save_gate.py           (new — 213 lines including module docstring + 6 pytest cases)

■ diff_summary

1. **src/phase_z2_pipeline.py** — Two additive blocks, no rewrites of existing lines:

   - **New helper `_persist_ai_repair_proposals_to_cache(...)`** inserted between `_check_post_ai_coverage_invariant` (line 705) and `_summarize_ai_repair_status` (line 770). The helper:
     - Imports `cache.save_proposal`, `AiFallbackCacheGateError`, and `AiFallbackProposal` lazily inside the function so module-load-time AI=0 normal-path semantics stay unchanged (mirrors the IMP-47B u8 surface where the audit chain is kept off the import graph until pipeline runtime invokes it).
     - For each record: gate-checks `apply_status.startswith("applied:")` AND a serialised `proposal` dict AND a non-empty `cache_key` AND a `fingerprints` dict — any miss → `cache_save_status="not_applied"` (skip; no `save_proposal` call → no filesystem touch).
     - Reconstructs `AiFallbackProposal.model_validate(record["proposal"])`; pydantic validation failure → `cache_save_status="invalid_proposal:<ExcType>"` (loud surfacing, no raise).
     - Calls `cache.save_proposal(cache_key, proposal_obj, visual_check_passed=..., user_approved=..., auto_cache=..., fingerprints=...)`. The IMP-46 dual-gate truth table (`src/phase_z2_ai_fallback/cache.py:69-73`) is honoured one-to-one — three pass cells, five raise cells — because the helper forwards the booleans directly without re-deriving them.
     - Catches `AiFallbackCacheGateError` and writes `cache_save_status=f"gate_blocked:{exc}"` so the gate-block reason (visual_check / user_approved) flows into the same record dict; the cache module guarantees no filesystem write happened when the error fires (`save_proposal` raises BEFORE any `mkdir`/`write_text`, contract at `cache.py:181-182`).
     - On success: `cache_save_status="saved"`. Records remain dicts; no new keys are required at the existing IMP-47B u4/u5/u7 callers downstream — `_summarize_ai_repair_status` does not read `cache_save_status` (kept out of scope; cache layer is a hint, not part of the human-review surface).

   - **New call site** after 

DO NOT read all Gitea comments. Exit reports are binding contracts.

=== WORK PRINCIPLES ===
RULE 0 — PIPELINE-CONSTRUCTION (overrides all)
Build GENERAL Phase Z pipeline, NOT sample-passing. Never hardcode MDX 03/04/05.
Evaluate against all 32 frames. Failure must be explainable.
RULE 1: English only. RULE 2: Auto pipeline. RULE 3: Status=3-axis.
RULE 4: Scope-qualified. pytest -q tests. COMMIT SCOPE only.
RULE 5: Factual: value+path+upstream. RULE 6: git add specific files only.
RULE 7: No hardcoding. RULE 8: AI finds 1px first. RULE 9: LLM classifies, code composes.
RULE 10: Don't uncritically accept. RULE 11: Checkpoint. RULE 12: Full paths. RULE 13: Anchor sync.
PZ-1: AI=0 normal. PZ-2: 1turn=1step. PZ-3: No speculative. PZ-4: No silent shrink.

=== COMMENT FORMAT (P5b 2026-05-20 — STRICT, OVERRIDES ALL STAGE-SPECIFIC BODY RULES) ===
The FIRST non-empty line of EVERY Gitea comment MUST start with one of:
  [Claude #N] <stage description>
  [Codex #N] <stage description>

This rule applies to ALL stages (Stage 1 ~ Stage 6) and ALL issue types
(regular, execution-issue, audit-only). No prefix, no decoration, no banner,
no audit anchor before the agent header. Examples:

CORRECT:
  [Codex #3] Stage 2 simulation-plan review — IMP-24

  📌 Verification table
  ...

WRONG (orchestrator detect_agent will fail; stage cannot advance):
  📌 **[Claude #3] Stage 2 ...**
  ## [Codex #3] Stage 2 ...
  === IMPLEMENTATION_UNITS ===     (header missing entirely)
  Audit anchor: ...                (preface before header)

This first-line-strict rule OVERRIDES any stage-specific "body MUST contain
ONLY" rule (e.g., COMPACT_PLAN_RULE). Those body rules apply AFTER the
mandatory first-line agent header. Decorations / banners / anchors go on
line 2 or later.

=== CONSENSUS + REWIND (2026-05-16 lock) ===
Final line of every Codex review comment MUST be exactly one of:
  FINAL_CONSENSUS: YES
  FINAL_CONSENSUS: NO

YES REQUIRES === EVIDENCE === block (commands run, files checked, tests/commit/verified facts as stage requires).
NO evidence = REJECTED.

If NO, the comment MUST also include a line BEFORE FINAL_CONSENSUS:
  rewind_target: <enum>
Allowed ENUM:
  retry_same          # technical_fail ONLY (push network/permission, hook reject). FORBIDDEN for test-verify / final-close.
  continue_same       # Stage 3 ONLY — current unit verified OK, but more planned units remain. Not a failure.
  stage_1_review      # rewind to problem-review (root cause / scope-lock wrong)
  stage_2_plan        # rewind to simulation-plan (plan wrong / missing files / tests)
  stage_3_edit        # rewind to code-edit (implementation incomplete / scope creep)
  stage_4_verify     # rewind to test-verify (commit ok but missed regression)
  stage_5_push        # rewind to commit-push (post-push remote anomaly — rare)

stage_4_verify (test-verify) NO and stage_6 final-close NO: retry_same / continue_same FORBIDDEN. Must rewind to earlier stage.
Stage 3 (code-edit) unit progress: use continue_same per unit; FINAL_CONSENSUS: YES only when ALL implementation_units complete.

TEMP / LOCAL ARTIFACTS: .orchestrator/tmp/ and .orchestrator/drafts/ only.
Codex (verifier). Verify EVERY claim. Use the verification level required by the CURRENT STAGE.
Do NOT run full pytest unless the stage task explicitly requires it.

With FINAL_CONSENSUS: YES, include === EVIDENCE === block :
  === EVIDENCE ===
  Commands run: (list)
  Files checked: (list)
  Test results: (if Stage 2/4)
  Commit SHA: (if Stage 5)
  Verified facts: (list, if Stage 6)

With FINAL_CONSENSUS: NO, include rewind_target line BEFORE the consensus line :
  rewind_target: stage_1_review | stage_2_plan | stage_3_edit | stage_4_verify | stage_5_push | retry_same | continue_same
  FINAL_CONSENSUS: NO
Note: retry_same / continue_same FORBIDDEN for test-verify and final-close stages.
Stage 3 (code-edit): if the single executed unit is correct AND remaining_units is non-empty → continue_same.
Stage 3: if ALL implementation_units complete and verified → FINAL_CONSENSUS: YES.

Stage 3 EVIDENCE block MUST include EXACTLY one of these lines (strict format):
  Remaining units: [u2, u3, u4]      (bracketed list when units remain)
  Remaining units: none              (when all units complete)

[Codex #N].
POST comment: https://gitea.hmac.kr/api/v1/repos/Kyeongmin/C.E.L_Slide_test2/issues/76/comments | token $GITEA_TOKEN

=== ORCHESTRATOR DRAFT OUTPUT ===
After posting your normal Gitea comment, save the same comment body to:
  D:\ad-hoc\kei\design_agent\.orchestrator\drafts\76_stage_code-edit_codex_r3.md
Do not summarize or shorten the draft; it must mirror the Gitea comment body.
The Gitea comment remains required in this transition phase.
