[{"id": 22500, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22500", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #1] Stage 1 problem-review — IMP-33\n\n## 1. Source re-read + scope crosscheck\n\n- source axis = #43 I3 (frame-aware AI 자동 보완 layer — restructure/reject path 의 AI fallback 실선화)\n- pair = #62 (IMP-46 frame transformation cache layer — content-addressed cache, lookup before AI call + save after visual_check PASS + 사용자 OK)\n- dependencies declared = #17 (IMP-17 carve-out) closed, #40 (IMP-31 gate audit) closed, #42 (IMP-04b catalog 32) **still open**.\n- consumers declared = #39 (IMP-30 first-render invariant, needs-adaptation placeholder zone), #38 (IMP-29 frontend zone-level evidence bridge), chain partner IMP-34/IMP-35 (Step 16/17 overflow chain).\n\nClosure semantics caveat — #40 (IMP-31) closed via **doc-only audit** (commits `182aa7c`, `e10ec36`); it did **NOT** add runtime AI code. The audit itself explicitly states `Runtime AI module path = src/phase_z2_ai_fallback/ (not created this cycle)` ([`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md):31, :56). So \"verified\" in the issue-body dependency list = audit closure, not \"activation gate cleared\". This matters for §2 root cause.\n\n## 2. Root cause — the activation gate vs. the issue scope (3-axis)\n\n### Axis A — 3-condition AND activation gate (carve-out boundary, [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):25-31)\n\nCurrent state of the three conditions that must ALL hold before any runtime AI code lands in `phase_z2_*`:\n\n| # | Condition | State at HEAD (`c412f1e`) | Evidence |\n|---|---|---|---|\n| 1 | User GO — explicit axis activation | **AMBIGUOUS** — #61 issue itself can be read as User GO for IMP-33, but the existing carve-out gate language ([`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):27) demands \"명시적 axis activation 요청\"; an issue ticket alone has historically not counted (see #40 close — same ticket existed, gate stayed NOT CLEAR). Stage 2 needs explicit user lock on this point. |\n| 2 | B4 frame_selection evidence integration complete | **NOT CLEAR** | [`PHASE-Z-PIPELINE-STATUS-BOARD.md`](docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md):48 Step 9 ⚠ partial, \"B4 frame_selection 의 V4 evidence 미통합\"; :82-84 \"B4 v0 = catalog declaration order 만\"; :126 row (j) \"render path 활성화 (region marker partial 주입 / B4 → mapper 통합 / V4 evidence 통합)\" = **❌ pending**. |\n| 3 | IMP-04 catalog 32 + IMP-05 V4 fallback live | **catalog ✓ / V4 fallback ✓ / IMP-04b parent close ✗** | `grep -c 'template_id:' templates/phase_z2/catalog/frame_contracts.yaml` = **32** (clear). IMP-05 `lookup_v4_match_with_fallback` rank-2/3 live since `23d1b25` (clear). But #42 (IMP-04b umbrella) **still open** — the parent close ceremony has not happened. |\n\n**Verdict** = gate state has improved since IMP-31 audit (cat#3 partially clears; #42 still open), but conditions #1 and #2 remain unmet at HEAD. Issue #61 scope (`Anthropic().messages.create()` 실선 추가) crosses the carve-out boundary at exactly the point this gate is designed to guard.\n\n### Axis B — module path inconsistency (carve-out spec vs. issue body)\n\n| source | path |\n|---|---|\n| [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) implicit + [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md):31, :56 explicit | `src/phase_z2_ai_fallback/` (**package / directory**) |\n| #61 issue body | `src/phase_z2_ai_repair.py` (**single file**) |\n\nThese are not equivalent. The carve-out spec deliberately reserved a package boundary for isolation (one of IMP-17's design lockings). Stage 2 must pick a canonical path and update one document to match. Claude #1 preference = honor the carve-out spec (`src/phase_z2_ai_fallback/`), so IMP-31 gate-audit doc + IMP-17 carve-out doc do not have to be re-rewritten. The \"repair\" naming in #61 is more semantically narrow than the spec's \"fallback\" — repair implies after-the-fact correction; fallback covers both Step 12 restructure (proactive) AND Step 16/17 retry (after-the-fact). Use the broader name.\n\n### Axis C — hook surface vs. existing fallback layer\n\nIssue body claims the AI hook should sit `phase_z2_failure_router.py 의 fallback hook 위 layer`. But:\n\n- `src/phase_z2_failure_router.py` today is **classification + mapping only** ([`phase_z2_failure_router.py`](src/phase_z2_failure_router.py):7 docstring \"분류 + 매핑까지만\"). It does NOT execute any action. No fallback hook exists on it.\n- The actual deterministic salvage cascade hook lives at `src/phase_z2_pipeline.py:2004 _attempt_salvage_chain` (IMP-12 u8). This is the orchestrator that walks `cross_zone_redistribute → glue_compression → font_step_compression` and would be the natural pre-position for an AI cascade extension.\n- The IMP-12 cascade currently exits with `salvage_terminal_action` = `layout_adjust` / `frame_reselect` / `details_popup_escalation` (per L2025-L2027). These are the *intended* downstream steps for IMP-34 / IMP-35 (zone resize + popup escalation). AI fallback must slot *after* those deterministic terminals — not before — per [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):16 \"when retry router exhausts deterministic actions ... AND user-approved fallback budget remains, an AI proposal MAY be invoked\".\n\nThe hook site is therefore **after** the deterministic cascade and the (still-MISSING) IMP-34/IMP-35 terminals. IMP-33 cannot wire cleanly without IMP-34/IMP-35 in place — otherwise it would short-circuit the deterministic cascade and violate the carve-out's \"deterministic actions exhausted\" precondition.\n\nStep 12 restructure path (`_imp05_route_hint(label) == \"ai_adaptation_required\"` at `src/phase_z2_pipeline.py:570-580`) is a separate hook surface, also empty today — only deterministic emission of `candidate_evidence[].route_hint` exists ([`phase_z2_pipeline.py`](src/phase_z2_pipeline.py):664).\n\n## 3. Scope-lock proposal (binding boundaries — Stage 2 will refine)\n\n### (a) Behavior delta — what changes, what does NOT\n\n| axis | today | after IMP-33 |\n|---|---|---|\n| Step 12 restructure unit | `route_hint=\"ai_adaptation_required\"` emitted in `candidate_evidence` (`src/phase_z2_pipeline.py:664`); IMP-30 path synthesizes provisional zone with raw MDX (`src/phase_z2_pipeline.py:723-740`) | AI call invoked AFTER provisional synthesis AND BEFORE final.html write (or AFTER first render under cache miss) — proposes builder_options / partial_overrides / slot mapping for the provisional zone. Output unit = single zone's slot payload. Frame selection, layout selection, zone topology = unchanged (deterministic) |\n| Step 17 salvage chain terminal | `salvage_terminal_action` ∈ {layout_adjust, frame_reselect, details_popup_escalation, none} → exit with `salvage_passed=False` | AI fallback added as final terminal AFTER IMP-34/IMP-35 deterministic steps land. Pre-IMP-34/IMP-35: explicit `not_yet_wired` exit reason, no AI call (scope guard) |\n| `phase_z2_failure_router` mapping | 7 failure types → 7 actions (`NEXT_ACTION_BY_FAILURE` at `src/phase_z2_failure_router.py:94-102`) | unchanged in this IMP. New failure types `ai_proposal_rejected` / `ai_proposal_low_confidence` deferred to IMP-34/IMP-35 |\n| `APPLICATION_MODE_BY_V4_LABEL` (`src/phase_z2_pipeline.py:101`) | restructure → `(\"layout_or_region_change\", False, \"human_review\")` | restructure → `(\"layout_or_region_change\", False, \"ai_fallback\")` when fallback budget allows; `delegated_to` swap is the only schema delta. `human_review` preserved as the cascade terminal |\n| MDX raw_content | preserved verbatim (IMP-30 provisional) | **unchanged — strictly verbatim**. AI proposal operates on slot payload + builder options, NOT on MDX text |\n| `slide_status.overall` enum | PASS / RENDERED_WITH_VISUAL_REGRESSION / PARTIAL_COVERAGE / PARTIAL_COVERAGE_WITH_VISUAL_REGRESSION | **unchanged**. AI fallback adds additive counter `ai_fallback_attempts` / `ai_fallback_passes` only |\n| normal-path AI call count | 0 | **0 (locked)**. AI call ONLY when (Step 12 restructure route_hint) OR (Step 17 salvage cascade exhausted AND IMP-34/IMP-35 also exhausted). Path-tagged with `caller=\"phase_z2_ai_fallback\"` for trace + accounting |\n| Anthropic client construction | only `src/html_generator.py` / `src/kei_client.py` / `src/content_editor.py` / `src/pipeline.py` (all Phase R'/Q) | new `src/phase_z2_ai_fallback/` package. Phase Q `EDITOR_PROMPT` / Kei-API endpoint **NOT imported** ([`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):35-44, \"영구 단절\") |\n| model ID | hardcoded `claude-opus-4-6-20250415` at 3 Kei sites | **NOT hardcoded** in IMP-33. Pulled from `src/config.py` Settings (new field `phase_z2_ai_fallback_model: str = \"claude-opus-4-7-...\"`) with .env override. Issue-body's `claude-opus-4-6-20250415` is from the Kei call sites — already stale (latest is opus 4.7 per environment header). Stage 2 must pick a versioned default and put it in config, not in the AI module |\n\n### (b) Output schema (the novel surface)\n\nIssue body says: `builder_options 변경 / partial_overrides / slot mapping proposal (콘텐츠 단위, restructuring proposal 형식)`. None of these consumer surfaces exist yet. Stage 2 must lock the schema before any AI client code lands. Strawman (Codex round 1차 review 대상):\n\n```jsonc\n{\n \"ai_fallback_proposal\": {\n \"proposal_kind\": \"builder_options\" | \"partial_overrides\" | \"slot_mapping\",\n \"target\": {\n \"section_id\": \"...\",\n \"unit_id\": \"...\",\n \"template_id\": \"...\", // V4 rank-1 frame\n \"zone_position\": \"...\" // top / bottom_l / bottom_r etc.\n },\n \"builder_options\": { /* keys allowed by builder for that template_id */ } | null,\n \"partial_overrides\": { /* {slot_name: override_value} */ } | null,\n \"slot_mapping\": [ /* {mdx_h3_index, slot_name} */ ] | null,\n \"confidence\": 0.0~1.0,\n \"rationale\": \"...\", // AI-emitted explanation\n \"cache_signature\": \"...\", // content hash for IMP-46 lookup/store\n \"ai_call_id\": \"...\", // for retry/audit trace\n \"fallback_path\": \"step12_restructure\" | \"step17_cascade_terminal\"\n }\n}\n```\n\n**Strict constraint** : `builder_options` keys MUST be validated against the builder's declared option keys (a new contract at `src/phase_z2_mapper.py` or builder side); any key outside that set is rejected before apply. `partial_overrides` slot names MUST be in the frame contract's declared slots. `slot_mapping` indices MUST match the unit's MDX h3 count. No free-form HTML, no CSS strings, no MDX text — Stage 2 verify against IMP-17 §Forbidden.\n\n### (c) Retry / backoff / timeout policy\n\nStrawman (Stage 2 lock):\n\n- timeout = **60s** per AI call (single shot, no streaming required — output is a small JSON proposal)\n- max_retry = **2** (initial + 1 retry on transient failure: `anthropic.APITimeoutError`, `anthropic.APIConnectionError`, HTTP 429/5xx)\n- backoff = exponential with jitter, base 2s, cap 16s\n- circuit breaker = **3 consecutive failures across a single pipeline run** → disable AI fallback for the remainder of that run (write trace, fall through to human_review)\n- per-pipeline-run AI call budget = **5 calls max** (configurable in `src/config.py`; prevents runaway in cascade scenarios)\n- on `circuit_open` or `budget_exhausted` → emit `delegated_to=\"human_review\"` (preserves IMP-12 cascade terminal semantics)\n\n### (d) IMP-46 (cache) integration — pair-issue ordering\n\nIssue body says `AI 호출 결과는 IMP-46 cache 에 저장 (visual_check PASS + 사용자 OK 후)`. IMP-46 issue (#62) cache layer does NOT exist yet at HEAD. Cache MUST land first (or alongside) to satisfy:\n- determinism guardrail (no-hardcoding: \"cache hit 결과 = 결정론적 (같은 input → 같은 output)\")\n- retry/backoff cost control (otherwise every retry hits live API)\n- visual_check PASS + 사용자 OK gating (without cache layer, no save target)\n\n**Ordering options** (Stage 2 lock):\n- (P1) IMP-46 lands first as no-op stub (signature compute + JSON read/write + invalidation hook). IMP-33 calls into stub. Subsequent IMP-46 work fills the cache logic. Decouples implementation.\n- (P2) IMP-33 + IMP-46 land in single PR (matching the \"pair\" claim in #61). Atomic. Risk = scope creep, harder review.\n- (P3) IMP-33 lands without cache; cache wiring deferred to IMP-46. **Violates** the issue's own guardrail \"AI 호출 결과는 IMP-46 cache 에 저장\" and risks runaway API calls.\n\nClaude #1 preference = **P1** (no-op cache stub first, then incremental). Codex view in round 1차.\n\n### (e) Hook activation gating (the safe wiring shape)\n\nEven if Stage 2 picks \"land the runtime code\", the actual call site MUST be gated by an explicit boolean (default OFF) at the call site:\n\n```python\n# src/phase_z2_pipeline.py (Step 12 restructure path)\nif (\n _imp05_route_hint(unit.label) == \"ai_adaptation_required\"\n and settings.phase_z2_ai_fallback_enabled # default False\n and unit.phase_z_status == \"needs_adaptation\"\n):\n proposal = phase_z2_ai_fallback.propose_for_unit(unit, ...)\n```\n\nThis preserves IMP-17 carve-out's \"design-only state\" default (no behavior change at HEAD), while making activation a single-flag flip after the gate clears. Stage 2 must explicitly lock this flag default = False.\n\n## 4. Guardrails (Stage 2 binding)\n\n| # | guardrail | source |\n|---|---|---|\n| G1 | normal-path AI call count = **0**. AI call site ONLY at Step 12 restructure (route_hint `ai_adaptation_required` AND `phase_z2_ai_fallback_enabled=True`) OR Step 17 cascade terminal (after IMP-34/IMP-35 land) | `feedback_ai_isolation_contract`, PZ-1, [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):14-16 |\n| G2 | **MDX 원문 verbatim 보존** — AI proposal output schema CANNOT carry MDX text fields; rejected at schema validation | issue body explicit + [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):20, `feedback_phase_z_spacing_direction` |\n| G3 | Output schema = `builder_options` / `partial_overrides` / `slot_mapping` ONLY. **No raw HTML, no CSS, no frame contract creation, no layout / zone topology selection** | [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):21 |\n| G4 | automatic frame_swap **금지** — V4 rank 1 frame preserved as-is; AI fixes content-to-slot, not slot-to-content | `feedback_phase_z_spacing_direction` |\n| G5 | overflow chain order = zone_ratio_retry → cross_zone_redistribute → glue_compression → font_step_compression → layout_adjust (IMP-34) → frame_reselect → details_popup_escalation (IMP-35) → AI fallback → human_review. **AI is the second-to-last terminal, not the first salvage** | [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):16, IMP-12 cascade |\n| G6 | no-hardcoding — model ID, timeout, max_retry, backoff base, circuit breaker count, budget, all in `src/config.py` Settings (env-overridable). No sample-specific (mdx 03/04/05) branching in `src/phase_z2_ai_fallback/` | RULE 0, `feedback_no_hardcoding` |\n| G7 | dropped 절대 룰 — `text_block` / `table` / `image` / `details` slot 삭제 X. AI proposal cannot emit empty slot when the source unit has content for that slot | issue body explicit |\n| G8 | Phase Q assets (`EDITOR_PROMPT`, Kei-API endpoint, `content_editor.py` httpx+SSE retry shape) **영구 단절** — `src/phase_z2_ai_fallback/` imports nothing from `src/content_editor.py` / `src/kei_client.py` | [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):35-44 |\n| G9 | activation flag `phase_z2_ai_fallback_enabled` default **False** at HEAD merge. Behavior delta vs. HEAD = 0 unless explicitly enabled. CI default keeps flag off | safety + carve-out default-design-only |\n| G10 | per-run budget + circuit breaker enforced. No infinite-retry / runaway-cost path. `feedback_auto_pipeline_first` — no `review_required` injection between salvage cascade and AI fallback (auto pipeline = self-determining) | `feedback_auto_pipeline_first` |\n| G11 | IMP-46 cache call signature MUST be computed and looked up BEFORE Anthropic API call. cache hit → return cached proposal without API call (cost + determinism). cache miss → API call → on success path, **MAY** save (gated by visual_check PASS + 사용자 OK per IMP-46 spec) | #62 IMP-46 spec |\n| G12 | `candidate_evidence` schema (IMP-05 L2 / IMP-29 b4872ba consumer) **unchanged**. AI proposal trace lives under a NEW field `ai_fallback_trace` in debug.json, NOT inside `candidate_evidence` | IMP-05 L2 lock + IMP-29 frontend bridge |\n| G13 | IMP-30 first-render invariant **unchanged**. Provisional zone synthesis happens BEFORE AI call. AI call modifies the provisional zone's slot_payload; if AI fails, zone stays in `needs_adaptation` state (no regression to abort) | [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md):50 |\n| G14 | RULE 0 — evaluated against ALL 32 frames + ALL aligned MDX sample axes. No 03/04/05 case-specific dispatch in `phase_z2_ai_fallback` package | RULE 0 PIPELINE-CONSTRUCTION |\n| G15 | module path = `src/phase_z2_ai_fallback/` (package, per [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) reservation). **NOT** `src/phase_z2_ai_repair.py`. Issue body wording corrected at scope-lock | axis B above |\n\n## 5. Implementation slicing sketch (Stage 2 plan input — NOT binding)\n\nWave-1 ordering (preferred):\n\n1. **U1 — Config + flag plumbing** (src-touching, 0 behavior change)\n - `src/config.py` Settings : `phase_z2_ai_fallback_enabled: bool = False`, `phase_z2_ai_fallback_model: str = \"\"`, `phase_z2_ai_fallback_timeout_s: int = 60`, `phase_z2_ai_fallback_max_retry: int = 2`, `phase_z2_ai_fallback_per_run_budget: int = 5`, `phase_z2_ai_fallback_circuit_threshold: int = 3`.\n - .env.example update (no committed .env).\n - Test : default flag = False, env override path works.\n\n2. **U2 — `src/phase_z2_ai_fallback/` package skeleton**\n - `__init__.py` exports `propose_for_unit(unit, ...) -> Optional[AiFallbackProposal]`.\n - `client.py` : Anthropic client construction with retry/backoff/timeout/circuit-breaker primitives. NO `EDITOR_PROMPT` / Kei import.\n - `schema.py` : `AiFallbackProposal` dataclass + JSON schema for validation. Strict — no MDX text fields.\n - `prompts.py` : new prompt (fresh, not Phase Q). Input = V4 evidence + frame contract + Internal Region structure + content_object cardinality + raw MDX section text **read-only context**. Output = proposal JSON only.\n - `validate.py` : schema validator + builder option key whitelist check + slot name check + dropped-slot guard.\n - All early-exit on `not settings.phase_z2_ai_fallback_enabled`.\n\n3. **U3 — Step 12 hook (restructure path)**\n - `src/phase_z2_pipeline.py` mapper / Step 12 site : when `_imp05_route_hint(unit.label) == \"ai_adaptation_required\"` AND flag ON AND unit is provisional → cache lookup → AI call → schema validate → apply proposal to slot_payload.\n - On failure (any) : preserve provisional shape, write trace, continue.\n - debug.json `ai_fallback_trace` field added (additive, optional).\n\n4. **U4 — Step 17 cascade terminal hook (deferred until IMP-34/IMP-35 land)**\n - `_attempt_salvage_chain` (`src/phase_z2_pipeline.py:2004`) : new terminal action `ai_fallback` added AFTER `details_popup_escalation` (IMP-35 landing).\n - Until IMP-34/IMP-35 land : explicit guard `salvage_terminal_action=\"ai_fallback_not_yet_wired\"`, no AI call in this path.\n - This unit may be split into its own follow-up IMP if Stage 2 decides scope is too wide.\n\n5. **U5 — IMP-46 stub integration**\n - `src/phase_z2_ai_fallback/cache.py` (or pair-issue handoff) : `compute_signature(unit, frame_contract, v4_evidence) -> str`, `lookup(signature) -> Optional[AiFallbackProposal]`, `save(signature, proposal)`.\n - U5 = no-op stub (signature compute returns deterministic hash; lookup returns None; save = no-op) when IMP-46 not landed; IMP-46 fills the body.\n\n6. **U6 — Tests**\n - Unit : prompt construction, schema validation, retry/backoff/circuit/budget.\n - Integration : Step 12 restructure unit → flag OFF → no AI call (byte-identical to HEAD). Flag ON + provisional zone → AI call invoked → proposal applied → final.html written.\n - RULE 0 cross-frame : 32 frames × representative MDX → no sample-specific branching observed in proposal output.\n - Phase Q isolation : `tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py` — assert no import from `src/content_editor.py` / `src/kei_client.py` / `src/html_generator.py` / `src/pipeline.py`.\n\n7. **U7 — docs**\n - [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) : flip \"design-only\" → \"active (gated by `phase_z2_ai_fallback_enabled`)\" when Stage 2 confirms gate clear.\n - [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md) : update gate state table + A1/A2 verdicts.\n - [`PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md`](docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md) : add IMP-33 + IMP-46 rows (currently missing — backlog stale relative to #61/#62 issue numbering).\n - [`PHASE-Z-PIPELINE-STATUS-BOARD.md`](docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md) §5 : \"runtime AI = Step 12 light_edit / restructure 1 곳만\" → add Step 17 terminal entry.\n\n## 6. Open questions (Codex round 1차 review 대상)\n\n- **Q1** — Activation gate clearance: is #61 issue body sufficient as \"User GO\" per [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md):27, or does an additional explicit user lock (commit message or separate ack) need to land first? (Conditions #2 B4 evidence and #3 IMP-04b parent close are also unmet.) Claude #1 preference = explicit user lock required at Stage 2 entry, even if issue exists.\n- **Q2** — Module path: `src/phase_z2_ai_fallback/` (per carve-out spec) vs `src/phase_z2_ai_repair.py` (per issue body). Claude #1 preference = package per spec.\n- **Q3** — Pair ordering: P1 (IMP-46 no-op stub first, then IMP-33) vs P2 (single PR pair) vs P3 (IMP-33 alone). Claude #1 preference = P1.\n- **Q4** — Step 17 cascade terminal: include in IMP-33 scope (with `not_yet_wired` guard until IMP-34/IMP-35 land) vs defer entirely to a follow-up IMP after IMP-34/IMP-35 close. Claude #1 preference = include (with guard) so the failure_router cascade has a registered terminal slot, avoiding stale `MISSING` status at `phase_z2_failure_router.NEXT_ACTION_IMPLEMENTATION_STATUS:142-149`.\n- **Q5** — Hook site for Step 12: at the `_imp05_route_hint` consumer in the mapper loop (`src/phase_z2_pipeline.py:3340~`) AFTER IMP-30 provisional synthesis, vs at a new dedicated hook function in `phase_z2_failure_router.py` (rename module to `phase_z2_router_and_fallback`?). Claude #1 preference = mapper loop hook, keep failure_router pure classification.\n- **Q6** — Model + budget defaults: which versioned model (e.g. `claude-opus-4-7-20260101` from env header vs `claude-opus-4-6-...` from Kei legacy), and what per-run AI call budget. Claude #1 strawman = latest opus 4.x, budget = 5. Codex view?\n- **Q7** — IMP-30 invariant interaction: AI call MUST run AFTER provisional zone synthesis. If AI fails, zone falls back to provisional (not abort). Verify against [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md):50 \"IMP-30 invariant change\" out-of-scope — does \"apply proposal to existing provisional zone's slot_payload\" count as invariant change? Claude #1 reading = no (slot_payload mutation is downstream of invariant; invariant is \"first render always exists\").\n- **Q8** — Cache visual_check + 사용자 OK gating: in P1 (stub-first) ordering, the visual_check + 사용자 OK gate cannot be enforced by the stub. Does IMP-33 land WITHOUT save (lookup-only) until IMP-46 visual_check gating is implemented? Claude #1 preference = yes (lookup-only in IMP-33; save deferred to IMP-46).\n\n=== EVIDENCE ===\n\n- Files read :\n - `src/phase_z2_failure_router.py` — L1-318 (full module — classification + mapping only, `NEXT_ACTION_IMPLEMENTATION_STATUS` at L142-149 shows `layout_adjust` / `frame_reselect` = MISSING; `cross_zone_redistribute` / `glue_compression` / `font_step_compression` = IMPLEMENTED via IMP-12 u7).\n - `src/phase_z2_pipeline.py` — L90-113 (V4_LABEL_TO_PHASE_Z_STATUS + MVP1_ALLOWED_STATUSES + APPLICATION_MODE_BY_V4_LABEL), L179-184 (V4Match.provisional field — IMP-30 u1), L566-585 (_IMP05_ROUTE_HINTS + _imp05_route_hint), L587-742 (lookup_v4_match_with_fallback + IMP-30 u1 allow_provisional synthesis), L2004-2076 (_attempt_salvage_chain — cascade orchestrator), L2829-2918 (Step 9 application_plan helpers IMP-32 u1/u2/u3), L4960-5040 (Step 17 salvage cascade call site + Step 18 failure_classification artifact write).\n - `src/content_editor.py` — L1-69 (Phase Q EDITOR_PROMPT — Archive Candidate, do not import per IMP-17 spec), L300-330 (Kei API streaming retry shape — reference only).\n - `src/config.py` — full file (3 fields: anthropic_api_key, kei_api_url, log_level).\n - `docs/architecture/IMP-17-CARVE-OUT.md` — full file (carve-out scope, 3-condition AND gate, Phase Q 단절 contract).\n - `docs/architecture/IMP-31-GATE-AUDIT.md` — full file (gate state table, issue-body axis verdict, future activation path declaration).\n - `docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md` — L1-167 (Step 9 ⚠ partial, row (j) ❌ pending, §5 \"runtime AI = Step 12 1 곳만\").\n - `docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md` — L60-72 (IMP-17 row, no IMP-33 / IMP-46 rows — backlog stale).\n - `docs/architecture/DORMANT-TRIGGERS.yaml` — L60-73 (IMP-17 carve-out trigger description).\n - `.orchestrator/issues/40_stage_problem-review_exit.md`, `.orchestrator/issues/41_stage_final-close_exit.md` (prior IMP-31/IMP-32 close evidence).\n - Issue #43 body (full I3 source axis description fetched via gitea API).\n - Issue #62 body (IMP-46 pair issue spec — cache signature, lookup/save semantics, invalidation rules).\n- Grep results :\n - `grep -c \"template_id:\" templates/phase_z2/catalog/frame_contracts.yaml` = **32** (gate condition #3 catalog clear).\n - `grep -n \"Anthropic\\|anthropic\"` in `src/` (non-pyc) = `src/content_editor.py:14`, `src/html_generator.py:20,605,1003`, `src/kei_client.py:585,594,691,713,917,979`, `src/pipeline.py:26,1782`. ALL Phase R'/Q sites. No Phase Z surface today. Hardcoded model = `claude-opus-4-6-20250415` at 3 kei_client sites only.\n - `grep -rn \"phase_z2_ai_fallback\\|phase_z2_ai_repair\"` = `src/phase_z2_ai_fallback/` not created at HEAD (per IMP-31 audit Stage 4 evidence + own re-verify); `phase_z2_ai_repair` referenced only in #61 issue body.\n - `grep -n \"_attempt_salvage_chain\"` = defined `src/phase_z2_pipeline.py:2004`, called L4976. Sole call site.\n - `grep -n \"_imp05_route_hint\\|ai_adaptation_required\"` = `src/phase_z2_pipeline.py:570, 575, 580, 664`. Route hint emitted in candidate_evidence; no consumer at HEAD.\n - `grep -n \"needs_adaptation\\|provisional\"` = `src/phase_z2_pipeline.py:179, 183, 594, 601-613, 717-740` (IMP-30 u1 provisional synthesis); `src/phase_z2_composition.py:376` (composition-layer needs-adaptation marker).\n- Commits cross-checked : IMP-17 (`e10ec36`), IMP-31 (`182aa7c`), IMP-30 (`1efbf67`), IMP-29 (`b4872ba`), IMP-28 L4 dedup (`265d70e`), IMP-32 u1~u5 (`c412f1e`), IMP-05 chain (`23d1b25`, `21476ae`, `15c5b9a`).\n- Gitea API : #17 closed, #40 closed, #42 **open**, #62 (IMP-46) open with body content fetched.\n- `.orchestrator/drafts/61_*` = none prior (Round 1 — confirmed via `ls`).\n- No code change made — Stage 1 problem-review only.\n\nrewind_target: 해당 없음 (YES 제안)\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T10:30:13+09:00", "updated_at": "2026-05-21T10:30:13+09:00"}, {"id": 22512, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22512", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #1] Stage 1 problem-review - IMP-33\n\nStage 1 verification result: Claude #1's root-cause direction is correct, with two scope locks that must be explicit before any implementation plan: the IMP-17 activation gate is still not clear, and the module path in issue #61 conflicts with the last audit contract.\n\n3-axis status:\n\n| Axis | Verified state | Evidence |\n|---|---|---|\n| Activation gate | NOT CLEAR | `docs/architecture/IMP-17-CARVE-OUT.md:25-31` requires User GO, B4 frame_selection evidence integration, and IMP-04 catalog + IMP-05 V4 fallback all at once. One missing condition keeps the carve-out design-only. |\n| B4 / frame evidence | NOT CLEAR | `docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md:48` says Step 9 is partial and V4 evidence is not integrated; `:82-84` says B4 v0 is catalog declaration order only; `:126` says render-path activation, B4 mapper integration, and V4 evidence integration are pending. |\n| Catalog / V4 fallback | PARTIAL | Local catalog count is now 32 `template_id:` entries, and `src/phase_z2_pipeline.py:587-599` confirms `lookup_v4_match_with_fallback` is live and does not call `calculate_fit`. However Gitea issue #42 is still open, so the umbrella closure/precondition is not complete. |\n\nRuntime AI scope check:\n\n- `src/phase_z2_ai_repair.py` does not exist.\n- `src/phase_z2_ai_fallback/` does not exist either, but `docs/architecture/IMP-31-GATE-AUDIT.md:31`, `:50`, and `:56` explicitly define that future runtime module path as `src/phase_z2_ai_fallback/` and say it was not created in that cycle.\n- `rg` found Anthropic calls in legacy/non-Phase-Z paths (`src/kei_client.py`, `src/html_generator.py`, `src/pipeline.py`), but no `Anthropic().messages.create()` / `messages.create()` runtime under `src/phase_z2_*`.\n- `src/phase_z2_failure_router.py:1-8` is still classification + mapping only; `:94-102` maps current failure types through cross-zone redistribution, glue compression, font-step compression, layout_adjust, frame_reselect, and none. It has no AI/cache route and states `details_popup_escalation` is not in the mapping at `:37-38`.\n\nScope-lock verdict:\n\n- Do not wire an Anthropic runtime call in this issue until the gate is explicitly unlocked or the issue is reframed as design-only.\n- If implementation later proceeds, the first Stage 2 item must resolve the path conflict: issue #61 names `src/phase_z2_ai_repair.py`, while the binding IMP-31 audit names `src/phase_z2_ai_fallback/`.\n- The valid output surface remains content-object / internal-region / frame-slot placement or restructuring proposals only. No MDX rewriting, HTML/CSS generation, automatic frame swap, sample-specific branching, or normal-path AI.\n- The fallback order must stay deterministic before AI: zone resize / responsive fit / popup / AI + cache only after the relevant prior deterministic stages exist and fail.\n\n=== EVIDENCE ===\nCommands run:\n- `Get-Content -LiteralPath 'D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\tmp\\codex_prompt.txt'`\n- `git status --short`\n- `rg -n \"User GO|axis activation|B4|frame_selection|catalog declaration|V4 evidence|lookup_v4_match_with_fallback|Runtime AI module|phase_z2_ai|Anthropic|messages\\.create|builder_options|partial_overrides|slot mapping|needs-adaptation|feedback_ai_isolation_contract|feedback_phase_z_spacing_direction|feedback_no_hardcoding\" docs src templates tests -S`\n- `rg --files docs src templates tests | rg \"(IMP-17|IMP-31|PIPELINE-STATUS|phase_z2_ai|failure_router|frame_contracts|feedback|phase_z2)\"`\n- `rg -c \"^\\s*template_id:\" templates/phase_z2/catalog/frame_contracts.yaml`\n- `Select-String -Path 'docs\\architecture\\IMP-17-CARVE-OUT.md' -Pattern 'User GO|B4 frame_selection|catalog|AI 호출|Forbidden|Anthropic|normal path' -Context 2,3`\n- `Select-String -Path 'docs\\architecture\\IMP-31-GATE-AUDIT.md' -Pattern 'User GO|B4 frame_selection|gate|Runtime AI module|Anthropic|not created|calculate_fit|forbidden' -Context 2,3`\n- `Select-String -Path 'docs\\architecture\\PHASE-Z-PIPELINE-STATUS-BOARD.md' -Pattern 'Step 9|B4|frame_selection|V4 evidence|pending|catalog declaration' -Context 2,3`\n- `rg -n \"def lookup_v4_match_with_fallback|calculate_fit|def .*failure|class .*Failure|next_action|fallback|AI|Anthropic|messages\\.create|phase_z2_ai\" src/phase_z2_pipeline.py src/phase_z2_failure_router.py src/phase_z2_router.py src/phase_z2_retry.py -S`\n- `Test-Path -LiteralPath 'src\\phase_z2_ai_repair.py'; Test-Path -LiteralPath 'src\\phase_z2_ai_fallback'`\n- `rg -n \"Anthropic\\(|messages\\.create\\(|claude-opus|phase_z2_ai_repair|phase_z2_ai_fallback\" src docs tests templates -S`\n- Gitea issue metadata reads for #17, #40, #42, and #61 via API; no comment history read.\nFiles checked:\n- `docs/architecture/IMP-17-CARVE-OUT.md`\n- `docs/architecture/IMP-31-GATE-AUDIT.md`\n- `docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md`\n- `src/phase_z2_pipeline.py`\n- `src/phase_z2_failure_router.py`\n- `src/phase_z2_router.py`\n- `src/phase_z2_retry.py`\n- `templates/phase_z2/catalog/frame_contracts.yaml`\nTest results:\n- Not run. Current stage explicitly says NO pytest.\nVerified facts:\n- HEAD is `c412f1e`.\n- Worktree is dirty before this review; unrelated tracked changes already exist in `templates/phase_z2/catalog/frame_contracts.yaml`, `templates/phase_z2/families/_WIP_FILES.md`, and `tests/test_family_contract_baseline.py`.\n- Local catalog count is 32 `template_id:` entries.\n- Gitea #17 is closed, #40 is closed, #42 is open, and #61 is open.\n- Both `src/phase_z2_ai_repair.py` and `src/phase_z2_ai_fallback/` are absent at HEAD.\n- No Phase Z runtime Anthropic call exists under `src/phase_z2_*`.\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T10:33:11+09:00", "updated_at": "2026-05-21T10:33:11+09:00"}, {"id": 22520, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22520", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #2] Stage 2 simulation-plan — IMP-33 #61\n\n=== IMPLEMENTATION_UNITS ===\n- id: u1\n summary: config flag plumbing — phase_z2_ai_fallback_enabled default OFF + model_id / timeout / max_retry / per_run_budget / circuit_threshold\n files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]\n tests: [tests/test_phase_z2_ai_fallback_config.py]\n estimate_lines: 40\n- id: u2\n summary: package + AiFallbackProposal schema — 3 proposal_kind; rejects mdx_text / frame_id / html / css; trace fields\n files: [src/phase_z2_ai_fallback/__init__.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]\n tests: [tests/phase_z2_ai_fallback/test_schema.py]\n estimate_lines: 50\n- id: u3\n summary: prompts.py — V4 evidence + frame_contract + Internal Region + cardinality + raw MDX read-only; no Phase Q\n files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]\n tests: [tests/phase_z2_ai_fallback/test_prompts.py]\n estimate_lines: 50\n- id: u4\n summary: client.py — Anthropic.messages.create wrapper; exp backoff w/ jitter; circuit + budget; APITimeoutError/Connection/429/5xx retryable\n files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]\n tests: [tests/phase_z2_ai_fallback/test_client_mock.py]\n estimate_lines: 50\n- id: u5\n summary: validate.py — schema + builder option whitelist + dropped-slot guard + frame-swap guard\n files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]\n tests: [tests/phase_z2_ai_fallback/test_validate.py]\n estimate_lines: 50\n- id: u6\n summary: cache.py — IMP-46 no-op stub (P1 ordering); save=no-op until cache layer lands\n files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]\n tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]\n estimate_lines: 35\n- id: u7\n summary: propose_for_unit() — gate → cache → prompt → client → validate; flag-off short-circuit\n files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]\n tests: [tests/phase_z2_ai_fallback/test_propose.py]\n estimate_lines: 50\n- id: u8\n summary: Step 12 hook — AFTER IMP-30 provisional; applies to slot_payload; debug.json ai_fallback_trace\n files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]\n tests: [tests/phase_z2/test_imp33_step12_hook.py]\n estimate_lines: 50\n- id: u9\n summary: Step 17 terminal — _attempt_salvage_chain ai_fallback after details_popup; BLOCKED_PENDING_IMP_34_35\n files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]\n tests: [tests/phase_z2/test_imp33_step17_guard.py]\n estimate_lines: 45\n- id: u10\n summary: Phase Q isolation — no import from content_editor/kei_client/html_generator/pipeline\n files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n estimate_lines: 35\n- id: u11\n summary: docs sync — IMP-17, IMP-31, STATUS-BOARD row(j), BACKLOG IMP-33 row\n files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]\n tests: []\n estimate_lines: 50\n\nPer-unit rationale:\n- u1-u7 = package. u1 flag OFF preserves IMP-17 design-only at HEAD. u2/u5 guard S4 + G2/G3/G6. u3 fresh prompt (Phase Q 단절). u4 numbers config-driven. u6 P1 stub.\n- u8/u9 = hooks. u8 AFTER IMP-30 provisional → invariant preserved on failure. u9 slot blocked behind IMP-34/IMP-35 (avoids stale MISSING).\n- u10 import guard; u11 docs lift after u1~u10 merge.\n\nCoverage: S1→u8/u9, S2→u2, S3→u3, S4→u2/u5, S5→u4, S6→u1; G1→u1/u7, G2→u2/u5, G3→u5, G4→u9, G5→u1, G6→u5, G7→u6/u7.\n\nOut-of-scope: IMP-46 logic (u6 stub), IMP-34/35 (u9 blocked), candidate_evidence (separate debug.json field), IMP-30 invariant, model id change, IMP-37, IMP-29.\n\nRollback: flag (u1) master switch — False disables all sites. Full revert = git revert u1~u11. HEAD byte-identical default OFF.\n\n=== EVIDENCE ===\n- Stage 1: module path = src/phase_z2_ai_fallback/ (package), not src/phase_z2_ai_repair.py.\n- src/phase_z2_pipeline.py:570-580 Step 12 hook; :587-742 IMP-30 provisional; :2004 _attempt_salvage_chain (u9).\n- src/phase_z2_failure_router.py:94-149 STATUS table (u9 extension).\n- docs/architecture/IMP-17-CARVE-OUT.md:14-31 carve-out + gate; :35-44 Phase Q 단절 (u10).\n- docs/architecture/IMP-31-GATE-AUDIT.md:25-31,:56 activation path.\n- src/config.py:1-20 Settings — u1 extends.\n- Memory: feedback_ai_isolation_contract, feedback_phase_z_spacing_direction, feedback_no_hardcoding, feedback_one_step_per_turn, feedback_ai_role_separation.\n- Filesystem: src/phase_z2_ai_fallback/ + tests/phase_z2_ai_fallback/ NOT PRESENT.\n- Open gate axes #2/#3 noted — flag-OFF default keeps HEAD gated regardless.\n\nrewind_target: 해당 없음 (YES 제안)\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T10:43:23+09:00", "updated_at": "2026-05-21T10:43:23+09:00"}, {"id": 22522, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22522", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #2] Stage 2 simulation-plan review - IMP-33 #61\n\n=== IMPLEMENTATION_UNITS ===\nreview:\n source_comment: Claude #2 Stage 2 simulation-plan\n units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]\n implementation_units_block_exists: true\n unit_size_rule:\n max_estimate_lines_allowed: 50\n max_files_allowed: 3\n result: pass\n completeness_result: fail\n\nPer-unit rationale:\n- u1-u7 create the intended package surface and keep the feature flag default OFF, but u3 does not explicitly include frame visual / figma_to_html partial input and u4 does not lock concrete timeout/max_retry values in the plan body.\n- u8 covers Step 12 restructure after IMP-30 provisional slot_payload, but the issue body also names reject route; Claude #2 does not state whether reject remains design_reference_only or gets an AI fallback path.\n- u9 is correctly marked blocked behind IMP-34/35, but the plan still claims a Step 17 terminal while not specifying the required order zone resize/responsive fit/popup/AI+cache through those missing deterministic stages.\n- u6 says IMP-46 cache is a no-op stub and save is no-op until the cache layer lands. That does not satisfy the issue guardrail that AI results are saved to the IMP-46 cache after visual_check PASS + user OK.\n- u10 is a good isolation test axis; u11 docs sync is useful, but docs do not repair the missing runtime-axis coverage above.\n\nOut-of-scope notes:\n- IMP-46 full cache, IMP-34/35 deterministic overflow terminals, IMP-37, IMP-29, and model upgrade are out of scope per issue body, but the Stage 2 plan must still define their interfaces/order when IMP-33 depends on them.\n- No code changes were made in this review.\n\nRollback strategy:\nClaude #2 gives a flag-based rollback, but the plan must be rewound because coverage is incomplete before implementation starts.\n\n=== EVIDENCE ===\nCommands run:\n- Get-Content D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\tmp\\codex_prompt.txt\n- git rev-parse HEAD\n- rg --files\n- Get-Content docs/architecture/IMP-17-CARVE-OUT.md\n- Get-Content docs/architecture/IMP-31-GATE-AUDIT.md\n- Get-Content docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md\n- Select-String src/phase_z2_pipeline.py for route hints, V4 fallback, salvage, slot_payload, debug.json\n- Get-Content src/phase_z2_failure_router.py\n- Get-Content src/config.py\n- Test-Path / Get-ChildItem for src/phase_z2_ai_fallback\n- pytest -q tests\n- Gitea API read for issue #61 comments page=1&limit=1; verified latest Stage 2 comment without reading full history\nFiles checked:\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\IMP-17-CARVE-OUT.md\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\IMP-31-GATE-AUDIT.md\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Z-PIPELINE-STATUS-BOARD.md\n- D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_pipeline.py\n- D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_failure_router.py\n- D:\\ad-hoc\\kei\\design_agent\\src\\config.py\nTest results:\n- pytest -q tests => 410 passed in 42.43s\nVerified facts:\n- HEAD is c412f1ea75085b2338aface13ddfdcda5240df50.\n- src/phase_z2_ai_fallback/ is not present at HEAD.\n- src/config.py currently has no Phase Z AI fallback flag/model/timeout/retry/budget settings.\n- src/phase_z2_failure_router.py is classification and next-action mapping only; layout_adjust and frame_reselect are MISSING, and details_popup_escalation is documented as not mapped.\n- src/phase_z2_pipeline.py has Step 12 slot_payload and _IMP05_ROUTE_HINTS route_hint emission, plus _attempt_salvage_chain at Step 17; terminal actions layout_adjust/frame_reselect/details_popup_escalation are still noted MISSING.\n- Claude #2 has 11 implementation units and none exceeds 50 estimated lines or 3 files.\n- Missing coverage: reject-route disposition, frame visual partial input, concrete retry/timeout values, full overflow-chain order through IMP-34/35, and IMP-46 save-after-PASS/user-OK cache guardrail.\nrewind_target: stage_2_plan\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T10:47:08+09:00", "updated_at": "2026-05-21T10:47:08+09:00"}, {"id": 22531, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22531", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #3] Stage 2 simulation-plan — IMP-33 #61 (R1 post-rewind)\n\n=== IMPLEMENTATION_UNITS ===\n- id: u1\n summary: Settings — ai_fallback_enabled=OFF; model=\"claude-opus-4-6-20250415\"; timeout=60s; max_retries=3; backoff 1.0/8.0/jitter0.3; budget=10; circuit=5\n files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]\n tests: [tests/test_phase_z2_ai_fallback_config.py]\n estimate_lines: 50\n- id: u2\n summary: AiFallbackProposal schema — proposal_kind∈{builder_options_patch,partial_overrides,slot_mapping_proposal}; rejects mdx_text/frame_id_change/raw_html/raw_css\n files: [src/phase_z2_ai_fallback/__init__.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]\n tests: [tests/phase_z2_ai_fallback/test_schema.py]\n estimate_lines: 50\n- id: u3\n summary: prompts — input=V4(route=ai_adaptation_required)+cardinality+frame_contract+frame_visual=templates/phase_z2/families/{tid}.html+figma_to_html_agent partial JSON+Internal Region+MDX READ-ONLY; output→u2\n files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]\n tests: [tests/phase_z2_ai_fallback/test_prompts.py]\n estimate_lines: 50\n- id: u4\n summary: client — anthropic.Anthropic(timeout=60s); backoff/budget/circuit via u1; retryable=transient(timeout/connection/429/5xx); NO inline literals\n files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]\n tests: [tests/phase_z2_ai_fallback/test_client_mock.py]\n estimate_lines: 50\n- id: u5\n summary: validate — schema+builder whitelist+dropped-slot guard(text/table/image/details)+frame-swap guard(V4 rank-1)+Internal Region containment\n files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]\n tests: [tests/phase_z2_ai_fallback/test_validate.py]\n estimate_lines: 50\n- id: u6\n summary: cache — IMP-46 protocol gate: read_proposal(key)→None; save_proposal(key,prop,*,visual_check_passed,user_approved) RAISES if either False (PASS+OK at IMP-33); storage→NotImplementedError(\"=IMP-46\")\n files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]\n tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]\n estimate_lines: 50\n- id: u7\n summary: propose_for_unit — flag-off→route gate(restructure OR Step17)→cache.read→u3→u4→u5; save_proposal CALLER-driven\n files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]\n tests: [tests/phase_z2_ai_fallback/test_propose.py]\n estimate_lines: 50\n- id: u8\n summary: Step 12 hook — restructure ONLY @pipeline.py:566-577. reject=design_reference_only NO AI (IMP-29 per :571,:576). AFTER IMP-30 provisional. debug.json+=ai_fallback_trace[]\n files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]\n tests: [tests/phase_z2/test_imp33_step12_hook.py]\n estimate_lines: 50\n- id: u9\n summary: Step 17 — extends _attempt_salvage_chain(:2004-2076). 8-step chain: 1)cross_zone_redistribute 2)glue_compression 3)font_step_compression [1-3 IMPL] 4)layout_adjust 5)frame_reselect [IMP-34] 6)details_popup_escalation [IMP-35] 7)ai_fallback+cache=THIS 8)user_override=IMP-29. u9=#7 after #6; emits 'ai_fallback_blocked_pending_imp_34_35'\n files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]\n tests: [tests/phase_z2/test_imp33_step17_guard.py]\n estimate_lines: 50\n- id: u10\n summary: AST import-guard — phase_z2_ai_fallback/* NOT import content_editor/kei_client/html_generator/src.pipeline\n files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n estimate_lines: 35\n- id: u11\n summary: docs — IMP-17(runtime WIRED-DEFAULT-OFF+reject vs restructure); IMP-31(module path); STATUS-BOARD Step12=WIRED-FLAG-OFF Step17=BLOCKED_PENDING_IMP_34_35; BACKLOG IMP-33; cascade+IMP-46 gate\n files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]\n tests: []\n estimate_lines: 50\n\nRationale (Codex#2 gaps→fix): u1=numbers; u3=input(frame_visual+figma_partial+IR+MDX READ-ONLY); u6=cache gate@IMP-33; u8=reject=design_reference_only no-AI; u9=8-step chain. u2/u5/u7=closed-system; u4=Settings-only; u10=isolation; u11=docs.\n\nOut-of-scope: IMP-46 storage; IMP-34/35; IMP-29; candidate_evidence; IMP-37; model upgrade.\n\nRollback: ai_fallback_enabled=False (u1) = master switch (u7/u8/u9 short-circuit). git revert u1~u11.\n\n=== EVIDENCE ===\n- IMP-31-GATE-AUDIT.md:31,50,56 (path); pipeline.py:566-577,2004-2076; failure_router.py:22-38,142-149 (layout_adjust/frame_reselect MISSING; popup not mapped); config.py:1-20; kei_client.py:594,597; templates/phase_z2/families/*.html.\n- Memory: feedback_ai_isolation_contract, feedback_phase_z_spacing_direction, feedback_no_hardcoding.\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T11:04:03+09:00", "updated_at": "2026-05-21T11:04:03+09:00"}, {"id": 22534, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22534", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #3] Stage 2 simulation-plan review - IMP-33 #61\n\n=== IMPLEMENTATION_UNITS ===\nreview:\n source_comment: Claude #3 Stage 2 simulation-plan R1 post-rewind\n units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]\n implementation_units_block_exists: true\n unit_size_rule:\n max_estimate_lines_allowed: 50\n max_files_allowed: 3\n result: pass\n completeness_result: pass\n prior_rewind_gaps_closed:\n frame_visual_and_figma_partial_input: u3\n concrete_timeout_retry_backoff: u1 and u4\n reject_route_disposition: u8\n imp46_pass_and_user_ok_cache_gate: u6\n step17_order_through_imp34_imp35_ai_cache: u9\n\nPer-unit rationale:\n- u1 locks default OFF plus model, timeout, retry, backoff, budget, and circuit settings in config scope.\n- u2 defines a proposal schema constrained to builder options, partial overrides, and slot mapping; forbidden MDX/frame/raw HTML/CSS mutations are explicit.\n- u3 now includes V4 route/cardinality, frame contract, frame visual, figma_to_html partial JSON, Internal Region, and MDX read-only input.\n- u4 routes Anthropic through configured timeout/retry/backoff policy and avoids inline timeout/retry literals.\n- u5 validates schema, allowed builder changes, dropped text/table/image/details guards, no frame swap, and Internal Region containment.\n- u6 satisfies the IMP-46 interface boundary by requiring visual_check_passed and user_approved before save, while storage remains blocked to IMP-46.\n- u7 composes the fallback path behind the feature flag, route gate, cache read, prompt/client/validate flow, with save remaining caller-driven.\n- u8 wires Step 12 only for restructure after IMP-30 provisional slot payload; reject remains design_reference_only with no AI.\n- u9 keeps Step 17 blocked until IMP-34/35 and specifies the required order ending in AI fallback plus cache after deterministic/popup stages.\n- u10 adds the isolation import guard so Phase Z AI fallback cannot import Phase Q/Kei/pipeline runtime surfaces.\n- u11 updates architecture/status docs to reflect default-off wiring, module path, Step 12/17 state, cascade order, and IMP-46 gate.\n\nOut-of-scope notes:\n- IMP-46 persistent cache storage, IMP-34/35 deterministic overflow terminals, IMP-29 frontend override, IMP-37 stage0 normalizer, and model upgrade remain out of scope.\n- No code changes were made in this review.\n\nRollback strategy:\n- Stage 2 has no code edits. Stage 3 rollback is the u1 master flag default OFF plus per-unit revert of the planned files.\n\n=== EVIDENCE ===\nCommands run:\n- Get-Content D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\tmp\\codex_prompt.txt\n- git rev-parse HEAD\n- git status --short\n- rg --files .orchestrator\n- rg -n \"Stage 2 simulation-plan|IMPLEMENTATION_UNITS|u9|u10|u11|FINAL_CONSENSUS\" .orchestrator\n- Get-Content .orchestrator\\drafts\\61_stage_simulation-plan_claude_r1.md\n- Get-Content .orchestrator\\drafts\\61_stage_simulation-plan_codex_r1.md\n- Get-Content .orchestrator\\issues\\61_stage_simulation-plan_failed.md\n- Get-Content docs\\architecture\\IMP-17-CARVE-OUT.md\n- Get-Content docs\\architecture\\IMP-31-GATE-AUDIT.md\n- Get-Content docs\\architecture\\PHASE-Z-PIPELINE-STATUS-BOARD.md\n- rg -n \"_attempt_salvage_chain|ai_adaptation_required|design_reference_only|route_hint|lookup_v4_match_with_fallback\" src tests docs\n- Get-Content src\\config.py\n- Get-Content src\\phase_z2_failure_router.py\n- Select-Object slices of src\\phase_z2_pipeline.py around route hints and _attempt_salvage_chain\n- Test-Path src\\phase_z2_ai_fallback\n- pytest -q tests\nFiles checked:\n- D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\drafts\\61_stage_simulation-plan_claude_r1.md\n- D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\issues\\61_stage_simulation-plan_failed.md\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\IMP-17-CARVE-OUT.md\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\IMP-31-GATE-AUDIT.md\n- D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Z-PIPELINE-STATUS-BOARD.md\n- D:\\ad-hoc\\kei\\design_agent\\src\\config.py\n- D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_failure_router.py\n- D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_pipeline.py\nTest results:\n- pytest -q tests => 410 passed in 41.84s\nVerified facts:\n- HEAD is c412f1ea75085b2338aface13ddfdcda5240df50.\n- src/phase_z2_ai_fallback/ is not present at HEAD, so the plan is additive.\n- Claude #3 has 11 implementation units; every unit is <=50 estimated lines and <=3 files.\n- The prior Codex #2 missing axes are covered by u3, u1/u4, u8, u6, and u9.\n- Normal path AI remains OFF by default and reject remains non-AI.\n- Step 17 AI is not made terminal ahead of IMP-34/35; it is ordered after deterministic resize/fit/popup stages and cache-gated.\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T11:07:15+09:00", "updated_at": "2026-05-21T11:07:15+09:00"}, {"id": 22537, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22537", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "⚠️ **[Orchestrator]** Stage 2 FINAL_CONSENSUS: YES rejected: no `- id: u` entry.\n\nRequirements (strict):\n- `=== IMPLEMENTATION_UNITS ===` block with at least one `- id: u` entry\n- Each unit MUST include `tests: [, ...]` (NOT `tests: []`)\n- Implementation + tests = same unit (no deferring tests to later units)\n\nExample:\n === IMPLEMENTATION_UNITS ===\n - id: u1\n summary: ...\n files: [...]\n tests: [tests/.../test_xxx.py]\n estimate_lines: \n", "assets": [], "created_at": "2026-05-21T11:07:35+09:00", "updated_at": "2026-05-21T11:07:35+09:00"}, {"id": 22538, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22538", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #4] Stage 2 simulation-plan — IMP-33 #61 (R2)\n\n=== IMPLEMENTATION_UNITS ===\n- id: u1\n summary: Settings — ai_fallback_enabled=OFF; model=\"claude-opus-4-6-20250415\"; timeout=60s; max_retries=3; backoff 1.0/8.0/jitter0.3; budget=10; circuit=5\n files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]\n tests: [tests/test_phase_z2_ai_fallback_config.py]\n estimate_lines: 50\n- id: u2\n summary: AiFallbackProposal schema — proposal_kind∈{builder_options_patch,partial_overrides,slot_mapping_proposal}; rejects mdx_text/frame_id_change/raw_html/raw_css\n files: [src/phase_z2_ai_fallback/__init__.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]\n tests: [tests/phase_z2_ai_fallback/test_schema.py]\n estimate_lines: 50\n- id: u3\n summary: prompts — input=V4(route=ai_adaptation_required)+cardinality+frame_contract+frame_visual=templates/phase_z2/families/{tid}.html+figma_to_html_agent partial JSON+Internal Region+MDX READ-ONLY; output→u2\n files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]\n tests: [tests/phase_z2_ai_fallback/test_prompts.py]\n estimate_lines: 50\n- id: u4\n summary: client — anthropic.Anthropic; timeout/retry/backoff/budget/circuit via u1; retryable=transient(timeout/connection/429/5xx); NO inline literals\n files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]\n tests: [tests/phase_z2_ai_fallback/test_client_mock.py]\n estimate_lines: 50\n- id: u5\n summary: validate — schema+builder whitelist+dropped-slot guard(text/table/image/details)+frame-swap guard(V4 rank-1)+Internal Region containment\n files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]\n tests: [tests/phase_z2_ai_fallback/test_validate.py]\n estimate_lines: 50\n- id: u6\n summary: cache — IMP-46 gate; read_proposal(key)→None; save_proposal(key,prop,*,visual_check_passed,user_approved) RAISES if either False; storage→NotImplementedError(\"=IMP-46\")\n files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]\n tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]\n estimate_lines: 50\n- id: u7\n summary: propose_for_unit — flag-off→route gate(restructure|Step17)→cache.read→u3→u4→u5; save_proposal CALLER-driven\n files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]\n tests: [tests/phase_z2_ai_fallback/test_propose.py]\n estimate_lines: 50\n- id: u8\n summary: Step 12 hook — restructure ONLY @pipeline.py:566-577. reject=design_reference_only NO AI (IMP-29 per :571,:576). AFTER IMP-30 provisional. debug.json+=ai_fallback_trace[]\n files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]\n tests: [tests/phase_z2/test_imp33_step12_hook.py]\n estimate_lines: 50\n- id: u9\n summary: Step 17 — extends _attempt_salvage_chain(:2004-2076). 8-rung — 1)cross_zone_redistribute 2)glue_compression 3)font_step[1-3 IMPL] 4)layout_adjust 5)frame_reselect[IMP-34] 6)popup[IMP-35] 7)ai_fallback+cache=THIS(BLOCKED→ai_fallback_blocked_pending_imp_34_35) 8)user_override=IMP-29\n files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]\n tests: [tests/phase_z2/test_imp33_step17_guard.py]\n estimate_lines: 50\n- id: u10\n summary: AST import-guard — phase_z2_ai_fallback/* NOT import content_editor/kei_client/html_generator/src.pipeline\n files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]\n estimate_lines: 35\n- id: u11\n summary: docs — IMP-17(runtime WIRED-DEFAULT-OFF+reject vs restructure); IMP-31(module path); STATUS-BOARD Step12=WIRED-FLAG-OFF Step17=BLOCKED_PENDING_IMP_34_35; cascade+IMP-46 gate\n files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]\n tests: []\n estimate_lines: 50\n\nRationale (Codex#2 gaps→R1 fix, retained): u1+u4=numbers; u3=frame_visual+figma_partial+IR+MDX READ-ONLY; u6=cache gate@IMP-33; u8=reject=design_reference_only no-AI; u9=8-rung w/ AI behind IMP-34/35; u2/u5/u7=closed; u10=isolation; u11=docs.\n\nOut-of-scope: IMP-46 storage; IMP-34/35; IMP-29; IMP-37; model upgrade.\n\nRollback: u1 ai_fallback_enabled=False = master switch. git revert u11→u1.\n\n=== EVIDENCE ===\n- IMP-31-GATE-AUDIT.md:31,50,56 (path binding)\n- pipeline.py:566-577 (restructure→ai_adaptation_required, reject→design_reference_only)\n- pipeline.py:2004-2076 (_attempt_salvage_chain extension)\n- failure_router.py:22-38,94-102,142-149 (layout_adjust/frame_reselect/popup NOT mapped)\n- config.py: ai_fallback_enabled absent @ HEAD c412f1e\n- src/phase_z2_ai_fallback/ not present (additive)\n- pytest -q tests → 410 passed (Codex#3 R1)\n- Memory: ai_isolation_contract, phase_z_spacing_direction, no_hardcoding, one_step_per_turn\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T11:12:21+09:00", "updated_at": "2026-05-21T11:12:21+09:00"}, {"id": 22540, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22540", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #4] Stage 2 simulation-plan review - IMP-33 #61\n\n=== IMPLEMENTATION_UNITS ===\nreview:\n source_comment: Claude #4 Stage 2 simulation-plan R2\n units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]\n implementation_units_block_exists: true\n unit_size_rule:\n max_estimate_lines_allowed: 50\n max_files_allowed: 3\n result: pass\n completeness_result: pass\n issue_axes_covered:\n fallback_only_ai_call: u1, u4, u7, u8, u9\n module_path: u2, u3, u4, u5, u6, u7, u10, u11\n input_contract: u3\n output_contract: u2, u5\n retry_timeout_backoff: u1, u4\n model_value: u1\n mdx_read_only: u2, u3, u5\n no_frame_swap: u5, u9\n overflow_chain_order: u9\n imp46_cache_gate: u6, u7\n normal_path_ai_zero: u1, u7, u8, u10\n\nPer-unit rationale:\n- u1 locks feature flag default OFF plus model, timeout, retry, backoff, budget, and circuit settings.\n- u2 constrains AI output to builder option patches, partial overrides, and slot mapping proposals; forbidden MDX/frame/raw HTML/CSS mutations are explicit.\n- u3 covers frame visual, figma_to_html partial JSON, V4 route/cardinality, frame contract, Internal Region, and MDX read-only prompt inputs.\n- u4 routes Anthropic through configured timeout/retry/backoff/budget/circuit policy with transient retry classes and no inline policy literals.\n- u5 validates schema, builder whitelist, no dropped text/table/image/details slots, V4 rank-1 frame preservation, and Internal Region containment.\n- u6 closes the prior cache gap by requiring visual_check_passed and user_approved before save while leaving persistent storage to IMP-46.\n- u7 composes flag-off, route-gated, cache-read, prompt/client/validate flow; save remains caller-driven after visual/user approval.\n- u8 wires Step 12 restructure only after IMP-30 provisional payload; reject remains design_reference_only and no-AI.\n- u9 correctly keeps Step 17 blocked behind IMP-34/35 and specifies deterministic/popup/AI+cache/user override order.\n- u10 adds an AST isolation guard against Phase Q/Kei/pipeline runtime imports from the fallback package.\n- u11 limits docs sync to the binding runtime state, module path, Step 12/17 state, cascade order, and IMP-46 gate.\n\nOut-of-scope notes:\n- Persistent IMP-46 cache storage, IMP-34/35 deterministic overflow terminals, IMP-29 frontend override, IMP-37 stage0 normalizer, and model upgrade remain out of scope.\n- No code changes were made in this review.\n\nRollback strategy:\n- Stage 2 has no code edits. Stage 3 rollback is the u1 master flag default OFF plus per-unit revert of the planned files.\n\n=== EVIDENCE ===\nCommands run:\n- Get-Content D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\tmp\\codex_prompt.txt\n- git rev-parse HEAD\n- git status --short\n- rg --files .orchestrator\n- Get-Content .orchestrator\\drafts\\61_stage_simulation-plan_claude_r2.md\n- Select-String .orchestrator\\drafts\\61_stage_simulation-plan_claude_r2.md for units/files/estimate_lines/final consensus\n- Get-Content line checks for docs\\architecture\\IMP-31-GATE-AUDIT.md, src\\phase_z2_pipeline.py, src\\phase_z2_failure_router.py\n- rg -n \"ai_fallback_enabled|phase_z2_ai_fallback|claude-opus-4-6\" src tests docs .orchestrator\\drafts\\61_stage_simulation-plan_claude_r2.md\n- Test-Path src\\config.py; Test-Path src\\phase_z2_ai_fallback\n- pytest -q tests\nFiles checked:\n- .orchestrator\\drafts\\61_stage_simulation-plan_claude_r2.md\n- docs\\architecture\\IMP-31-GATE-AUDIT.md\n- src\\phase_z2_pipeline.py\n- src\\phase_z2_failure_router.py\n- src\\config.py\nTest results:\n- pytest -q tests: 410 passed in 42.05s\nVerified facts:\n- HEAD = c412f1ea75085b2338aface13ddfdcda5240df50.\n- Claude R2 plan body is 4928 chars and contains u1-u11, each estimate_lines <= 50 and files <= 3.\n- IMP-31-GATE-AUDIT binds runtime module path to src/phase_z2_ai_fallback/ and says it is not yet created.\n- src/phase_z2_ai_fallback/ is absent at HEAD; planned package is additive.\n- src/config.py exists and ai_fallback_enabled is absent at HEAD.\n- src/phase_z2_pipeline.py:566-577 maps restructure to ai_adaptation_required and reject to design_reference_only.\n- src/phase_z2_pipeline.py:2004-2076 contains the current deterministic salvage chain extension point.\n- src/phase_z2_failure_router.py:94-102 maps font_step_insufficient to layout_adjust and rerender_still_fails to frame_reselect; lines 142-149 mark layout_adjust/frame_reselect missing.\n\nFINAL_CONSENSUS: YES", "assets": [], "created_at": "2026-05-21T11:14:57+09:00", "updated_at": "2026-05-21T11:14:57+09:00"}, {"id": 22542, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22542", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #5] Stage 3 code-edit — IMP-33 #61 (R1 u1)\n\nExecuting unit: u1\n\n■ unit_executed\n- id: u1\n- summary: AI fallback Settings defaults locked (master flag OFF + model + timeout + retry/backoff + budget + circuit).\n\n■ files_changed\n- src/config.py\n- tests/test_phase_z2_ai_fallback_config.py\n\n■ diff_summary\n- `src/config.py` — added 9 fields to `Settings` under an IMP-33 u1 marker comment, all fallback-only policy knobs sourced from Stage 2 plan:\n - `ai_fallback_enabled: bool = False`\n - `ai_fallback_model: str = \"claude-opus-4-6-20250415\"`\n - `ai_fallback_timeout_s: float = 60.0`\n - `ai_fallback_max_retries: int = 3`\n - `ai_fallback_backoff_base_s: float = 1.0`\n - `ai_fallback_backoff_cap_s: float = 8.0`\n - `ai_fallback_backoff_jitter: float = 0.3`\n - `ai_fallback_budget_per_run: int = 10`\n - `ai_fallback_circuit_breaker_threshold: int = 5`\n- `tests/test_phase_z2_ai_fallback_config.py` — NEW. 4 tests pinning the locked defaults (master flag OFF, model id, retry/timeout/backoff, budget+circuit). Docstring records that downstream u4 must source policy from Settings (no inline literals).\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/test_phase_z2_ai_fallback_config.py -q` → 4 passed in 0.25s.\n- No other test files touched; normal-path AI call count unchanged (flag default OFF, no client wiring in this unit).\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — master flag default OFF; no client/router/Anthropic import added in u1.\n- feedback_ai_isolation_contract: preserved — settings module change only; no MDX rewrite path introduced.\n- RULE 7 (no hardcoding): defaults are policy knobs, not sample-specific case branches. No MDX/frame id literals introduced.\n- feedback_one_step_per_turn: only u1 executed; stopping here.\n\n■ remaining_units\n- u2 (AiFallbackProposal schema — proposal_kind whitelist, MDX/frame_id/raw_html/raw_css reject)\n- u3 (prompts.py — input contract, MDX READ-ONLY)\n- u4 (client.py — Anthropic wiring, timeout/retry/backoff/budget/circuit via u1)\n- u5 (validate.py — schema + builder whitelist + dropped-slot + frame-swap + Internal Region containment)\n- u6 (cache.py — IMP-46 gate; storage NotImplementedError)\n- u7 (router.py — flag-off + route-gated + cache-read composition)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order enforced)\n- u10 (AST import isolation guard — no Phase Q/Kei/pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u1.\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u2 (schema). Module path = `src/phase_z2_ai_fallback/` per Stage 1 audit-binding (IMP-31-GATE-AUDIT). No commit / push performed this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:18:44+09:00", "updated_at": "2026-05-21T11:18:44+09:00"}, {"id": 22544, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22544", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #5] Stage 3 code-edit review - IMP-33 #61 (R1 u1)\n\nunit_verified: u1\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/config.py tests/test_phase_z2_ai_fallback_config.py\n- Get-Content -LiteralPath src/config.py\n- Get-Content -LiteralPath tests/test_phase_z2_ai_fallback_config.py\n- python -m pytest tests/test_phase_z2_ai_fallback_config.py -q\n- rg -n \"Anthropic|messages\\.create|ai_fallback\" src tests/test_phase_z2_ai_fallback_config.py\n\nFiles checked:\n- src/config.py\n- tests/test_phase_z2_ai_fallback_config.py\n\nTest results:\n- python -m pytest tests/test_phase_z2_ai_fallback_config.py -q: 4 passed in 0.02s\n\nVerified facts:\n- src/config.py adds the u1 Settings policy fields with the planned values: ai_fallback_enabled=False, model=claude-opus-4-6-20250415, timeout=60.0, max_retries=3, backoff base/cap/jitter=1.0/8.0/0.3, budget=10, circuit threshold=5.\n- tests/test_phase_z2_ai_fallback_config.py pins the master flag, model, retry/timeout/backoff, budget, and circuit defaults.\n- The u1 diff adds no fallback client, router, Anthropic import, or messages.create call; normal-path AI call count is unchanged by this unit.\n- git status shows unrelated pre-existing worktree changes outside this u1 review scope; the verified u1 files are src/config.py and tests/test_phase_z2_ai_fallback_config.py only.\n- This review verified only the single executed unit u1.\n\nRemaining units: [u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T11:20:32+09:00", "updated_at": "2026-05-21T11:20:32+09:00"}, {"id": 22551, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22551", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #6] Stage 3 code-edit — IMP-33 #61 (R2 u2)\n\nExecuting unit: u2\n\n■ unit_executed\n- id: u2\n- summary: AiFallbackProposal schema — proposal_kind whitelist (builder_options_patch / partial_overrides / slot_mapping_proposal) + explicit rejection of mdx_text / frame_id_change / raw_html / raw_css; extra-fields forbidden.\n\n■ files_changed\n- src/phase_z2_ai_fallback/__init__.py (NEW, 15 lines)\n- src/phase_z2_ai_fallback/schema.py (NEW, 50 lines)\n- tests/phase_z2_ai_fallback/__init__.py (NEW, 0 bytes — pytest package marker, mirrors `tests/phase_z2/__init__.py` convention)\n- tests/phase_z2_ai_fallback/test_schema.py (NEW, 46 lines)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/__init__.py` — package init at the path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`). Re-exports `AiFallbackProposal`, `ProposalKind`. Docstring records the fallback-only contract (`feedback_ai_isolation_contract`).\n- `src/phase_z2_ai_fallback/schema.py` — Pydantic v2 model `AiFallbackProposal` with:\n - `ProposalKind` (str-Enum) = `{builder_options_patch, partial_overrides, slot_mapping_proposal}`\n - `FORBIDDEN_KINDS` = `{mdx_text, frame_id_change, raw_html, raw_css}` rejected at field-validator (mode=\"before\") so a JSON payload containing a forbidden literal raises `ValidationError` before Enum coercion.\n - `model_config = ConfigDict(extra=\"forbid\")` — blocks AI from smuggling forbidden fields (e.g. `raw_html=...`) alongside a valid `proposal_kind`.\n - `payload: dict[str, Any]` + `rationale: str` — payload kept opaque at u2 (u5 validator will enforce builder whitelist / dropped-slot guard / frame-swap guard / Internal Region containment).\n- `tests/phase_z2_ai_fallback/__init__.py` — empty package marker.\n- `tests/phase_z2_ai_fallback/test_schema.py` — 9 tests:\n - parametrized: each of the 3 whitelisted `proposal_kind` strings accepted (3 tests)\n - parametrized: each of the 4 forbidden strings rejected with `ValidationError` (4 tests)\n - unknown kind rejected (1 test)\n - `extra=\"forbid\"` blocks `raw_html=...` smuggled alongside a valid kind (1 test)\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_schema.py -q` → 9 passed in 0.04s.\n- Import sanity: `from src.phase_z2_ai_fallback import AiFallbackProposal, ProposalKind` succeeds; `list(ProposalKind)` returns the 3 whitelisted members.\n- No edits to `src/phase_z2_pipeline*.py`, no Anthropic import, no `messages.create()` call → normal-path AI call count unchanged.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — schema-only addition, no runtime call site introduced.\n- `feedback_ai_isolation_contract`: preserved — schema explicitly forbids `mdx_text` (MDX read-only), so even when wired later it cannot rewrite MDX.\n- `feedback_phase_z_spacing_direction`: preserved — schema forbids `frame_id_change`, protecting V4 rank-1 (u5 will reinforce at validate layer).\n- RULE 7 (no hardcoding): no sample-specific case branches; `proposal_kind` whitelist is policy, not sample data.\n- RULE 9 (LLM classifies, code composes): schema treats AI output as a *proposal*, not as raw HTML/CSS — composition stays in code.\n- Stage 2 unit_size_rule (≤50 lines, ≤3 files): the 3 logical files match the plan exactly; the empty `tests/phase_z2_ai_fallback/__init__.py` package marker is a 0-byte pytest convention (mirrors `tests/phase_z2/__init__.py`) and carries no logical change. Flagging transparently.\n- `feedback_one_step_per_turn`: only u2 executed; stopping here.\n\n■ remaining_units\n- u3 (prompts.py — input contract: V4 route+cardinality+frame_contract+frame_visual+figma_to_html partial+Internal Region; MDX READ-ONLY)\n- u4 (client.py — Anthropic wiring, timeout/retry/backoff/budget/circuit sourced from u1 Settings; no inline literals)\n- u5 (validate.py — schema + builder whitelist + dropped-slot (text/table/image/details) + frame-swap guard (V4 rank-1) + Internal Region containment)\n- u6 (cache.py — IMP-46 gate; visual_check_passed AND user_approved required; storage NotImplementedError)\n- u7 (router.py — flag-off + route-gated + cache-read composition; save caller-driven)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order enforced)\n- u10 (AST import isolation guard — no Phase Q/Kei/pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u2.\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u3 (prompts.py). Inputs per Stage 2 plan: V4(route=ai_adaptation_required) + cardinality + frame_contract + frame_visual=`templates/phase_z2/families/{tid}.html` + figma_to_html_agent partial JSON + Internal Region + MDX READ-ONLY. No commit / push this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:23:25+09:00", "updated_at": "2026-05-21T11:23:25+09:00"}, {"id": 22553, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22553", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #6] Stage 3 code-edit review - IMP-33 #61 (R2 u2)\n\nunit_verified: u2\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/__init__.py src/phase_z2_ai_fallback/schema.py tests/phase_z2_ai_fallback/__init__.py tests/phase_z2_ai_fallback/test_schema.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/schema.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/__init__.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_schema.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/__init__.py\n- python -m pytest tests/phase_z2_ai_fallback/test_schema.py -q\n- rg -n \"Anthropic|messages\\.create|ai_fallback\" src/phase_z2_ai_fallback tests/phase_z2_ai_fallback/test_schema.py\n- python -c \"from src.phase_z2_ai_fallback import AiFallbackProposal, ProposalKind; print(AiFallbackProposal.__name__); print([kind.value for kind in ProposalKind])\"\n- line counts for src/phase_z2_ai_fallback/schema.py, src/phase_z2_ai_fallback/__init__.py, tests/phase_z2_ai_fallback/test_schema.py\n\nFiles checked:\n- src/phase_z2_ai_fallback/__init__.py\n- src/phase_z2_ai_fallback/schema.py\n- tests/phase_z2_ai_fallback/__init__.py\n- tests/phase_z2_ai_fallback/test_schema.py\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_schema.py -q: 9 passed in 0.02s\n\nVerified facts:\n- src/phase_z2_ai_fallback/schema.py defines ProposalKind with exactly the three planned allowed values: builder_options_patch, partial_overrides, slot_mapping_proposal.\n- AiFallbackProposal uses pydantic ConfigDict(extra=\"forbid\"), has proposal_kind, payload, and rationale fields, and rejects forbidden proposal_kind strings mdx_text, frame_id_change, raw_html, and raw_css before Enum coercion.\n- tests/phase_z2_ai_fallback/test_schema.py covers accepted whitelisted kinds, all four forbidden kinds, an unknown kind, and extra-field rejection for a raw_html smuggling attempt.\n- src/phase_z2_ai_fallback/__init__.py re-exports AiFallbackProposal and ProposalKind from the audit-locked module path.\n- Import sanity succeeds and lists only the three whitelisted ProposalKind values.\n- rg found no Anthropic import and no messages.create call in src/phase_z2_ai_fallback or the u2 schema test; the only matches were package/test import references containing ai_fallback.\n- No pipeline wiring, client, router, or runtime AI call site is added by this unit; normal-path AI call count is unchanged by u2.\n- git status shows unrelated pre-existing worktree changes outside this u2 review scope; the verified u2 files are the four files listed above only.\n- This review verified only the single executed unit u2.\n\nRemaining units: [u3, u4, u5, u6, u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO", "assets": [], "created_at": "2026-05-21T11:24:50+09:00", "updated_at": "2026-05-21T11:24:50+09:00"}, {"id": 22555, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22555", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #7] Stage 3 code-edit — IMP-33 #61 (R3 u3)\n\nExecuting unit: u3\n\n■ unit_executed\n- id: u3\n- summary: AI fallback prompt builder (`src/phase_z2_ai_fallback/prompts.py`). System prompt pins MDX READ-ONLY + u2 whitelist + forbidden kinds + frame_id-swap lock + Internal Region containment. User payload carries the 6 declared inputs. Fallback-route guard raises `ValueError` when V4 route ≠ `ai_adaptation_required`.\n\n■ files_changed\n- src/phase_z2_ai_fallback/prompts.py (NEW, 80 lines)\n- tests/phase_z2_ai_fallback/test_prompts.py (NEW, 100 lines, 11 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/prompts.py`:\n - module-level constant `V4_ROUTE_AI_ADAPTATION = \"ai_adaptation_required\"` (mirrors `src/phase_z2_pipeline.py:575` `_IMP05_ROUTE_HINTS[\"restructure\"]`).\n - `SYSTEM_PROMPT` (module constant) — composes:\n - Rule 1: MDX in user payload is **READ-ONLY**; no rewrite / compress / paraphrase (`feedback_ai_isolation_contract`).\n - Rule 2: output MUST be a single JSON object conforming to `AiFallbackProposal` (u2).\n - Rule 3: enumerates the u2 whitelist `{builder_options_patch, partial_overrides, slot_mapping_proposal}` from `ProposalKind` (no hardcoded duplicate; sourced from u2 module).\n - Rule 4: enumerates the u2 forbidden kinds `{mdx_text, frame_id_change, raw_html, raw_css}` from `FORBIDDEN_KINDS` (single source of truth).\n - Rule 5: forbids `frame_id` change (V4 rank-1 protected, `feedback_phase_z_spacing_direction`).\n - Rule 6: keep declared frame slots (text/table/image/details) populated (Stage 2 dropped-slot guard preview; u5 enforces).\n - Rule 7: respect Internal Region containment.\n - `build_ai_fallback_prompt(*, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text) -> dict[str, str]`:\n - Fallback-route guard: reads `v4_result.route` (or alias `imp05_route_hint`); raises `ValueError` mentioning `V4_ROUTE_AI_ADAPTATION` if not equal. This is the prompt-layer enforcement of PZ-1 (AI=0 normal path).\n - Returns `{\"system\": SYSTEM_PROMPT, \"user\": json.dumps(payload, ensure_ascii=False)}`.\n - User payload structure: `{\"v4\": {route, cardinality, label, frame_id, rank}, \"frame_contract\", \"frame_visual_html\", \"figma_partial_json\", \"internal_region\", \"mdx_text_READ_ONLY\"}` — MDX key suffix is the on-wire READ-ONLY signal (paired with system-prompt rule 1).\n - Cardinality alias support: reads `cardinality` or `cardinality_signature` (V4 caller compatibility).\n - No Anthropic import, no `messages.create()` call, no filesystem I/O (caller supplies loaded artifacts) — u4 owns client wiring.\n- `tests/phase_z2_ai_fallback/test_prompts.py` — 11 tests:\n - SYSTEM_PROMPT declares \"READ-ONLY\".\n - SYSTEM_PROMPT enumerates all 3 whitelisted `ProposalKind` values.\n - SYSTEM_PROMPT enumerates all 4 forbidden kinds from `FORBIDDEN_KINDS`.\n - SYSTEM_PROMPT mentions `frame_id` (swap lock).\n - `build_ai_fallback_prompt` returns exactly the `{system, user}` shape and `system == SYSTEM_PROMPT`.\n - User payload contains all 6 declared inputs, the V4 sub-shape, and the MDX key suffixed `_READ_ONLY` (asserts `\"mdx_text\" not in payload` to confirm there is no writable MDX alias).\n - Parametrized: `direct_render`, `deterministic_minor_adjustment`, `design_reference_only`, missing-route — all raise `ValueError` matching `V4_ROUTE_AI_ADAPTATION`.\n - `cardinality_signature` alias is read when `cardinality` is absent.\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_prompts.py -q` → 11 passed in 0.02s.\n- `python -m pytest tests/phase_z2_ai_fallback/ -q` → 20 passed in 0.03s (schema 9 + prompts 11).\n- Baseline + u1 + u2 + u3 combined: `python -m pytest tests/test_family_contract_baseline.py tests/test_phase_z2_ai_fallback_config.py tests/phase_z2_ai_fallback/ -q` → 28 passed in 0.62s. No regression in baseline.\n- `grep -rn \"Anthropic|messages\\.create\" src/phase_z2_ai_fallback/ tests/phase_z2_ai_fallback/` → only a docstring reference to \"the Anthropic client (u4)\"; no import, no API call.\n- No edits to `src/phase_z2_pipeline*.py`; normal-path AI call count unchanged.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — prompts builder is module-scoped, callable only when V4 route equals `ai_adaptation_required`; raises before constructing prompt otherwise. No call-site introduced yet (u7 wires).\n- `feedback_ai_isolation_contract`: preserved — MDX text is explicitly suffixed `_READ_ONLY` and the system prompt declares MDX is read-only.\n- `feedback_phase_z_spacing_direction`: preserved — system prompt forbids `frame_id` change; u5 will reinforce at the validator.\n- RULE 7 (no hardcoding): the whitelist + forbidden kind lists are derived from `ProposalKind` and `FORBIDDEN_KINDS` (single source of truth in u2). No sample-specific MDX/frame literal in this module.\n- RULE 9 (LLM classifies, code composes): u3 hands the LLM a *classification/proposal* task only; payload composition (and HTML/CSS structure) remain code-owned downstream.\n- `feedback_one_step_per_turn`: only u3 executed; stopping here. u4 NOT touched.\n\n■ remaining_units\n- u4 (client.py — Anthropic wiring; timeout/retry/backoff/budget/circuit via u1 Settings; transient-retry classes; NO inline literals)\n- u5 (validate.py — schema + builder whitelist + dropped-slot + frame-swap + Internal Region containment)\n- u6 (cache.py — IMP-46 gate; storage NotImplementedError)\n- u7 (router.py — flag-off + route-gated + cache-read composition)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order enforced)\n- u10 (AST import isolation guard — no Phase Q/Kei/pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u3. (Note: u3 deliberately does NOT load HTML/JSON from disk — that becomes the u7 router's responsibility; not a new axis, planned in Stage 2.)\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u4 (client). Module path = `src/phase_z2_ai_fallback/client.py` per Stage 1 audit-binding. All policy values (timeout=60s, max_retries=3, backoff base/cap/jitter=1.0/8.0/0.3, budget=10, circuit=5, model=`claude-opus-4-6-20250415`) MUST source from u1 `Settings` (no inline literals; u4 test asserts this). No commit / push performed this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:30:12+09:00", "updated_at": "2026-05-21T11:30:12+09:00"}, {"id": 22558, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22558", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #7] Stage 3 code-edit review - IMP-33 #61 (R3 u3)\n\nunit_verified: u3\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/prompts.py tests/phase_z2_ai_fallback/test_prompts.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/prompts.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_prompts.py\n- python -m pytest tests/phase_z2_ai_fallback/test_prompts.py -q\n- rg -n \"Anthropic|messages\\.create|build_ai_fallback_prompt|V4_ROUTE_AI_ADAPTATION|mdx_text_READ_ONLY\" src/phase_z2_ai_fallback tests/phase_z2_ai_fallback/test_prompts.py\n- line counts for src/phase_z2_ai_fallback/prompts.py and tests/phase_z2_ai_fallback/test_prompts.py\n- python import/build sanity for build_ai_fallback_prompt with cardinality_signature alias\n\nFiles checked:\n- src/phase_z2_ai_fallback/prompts.py\n- tests/phase_z2_ai_fallback/test_prompts.py\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_prompts.py -q: 11 passed in 0.02s\n\nVerified facts:\n- src/phase_z2_ai_fallback/prompts.py defines V4_ROUTE_AI_ADAPTATION as ai_adaptation_required and rejects prompt construction for non-AI routes by raising ValueError.\n- SYSTEM_PROMPT declares MDX READ-ONLY, requires AiFallbackProposal output, enumerates the u2 ProposalKind whitelist, enumerates FORBIDDEN_KINDS, forbids frame_id changes, requires text/table/image/details slots to stay populated, and requires Internal Region containment.\n- build_ai_fallback_prompt returns exactly system and user keys; the user JSON carries v4 route/cardinality/label/frame_id/rank plus frame_contract, frame_visual_html, figma_partial_json, internal_region, and mdx_text_READ_ONLY.\n- The cardinality_signature alias is accepted when cardinality is absent.\n- tests/phase_z2_ai_fallback/test_prompts.py covers READ-ONLY prompt text, whitelisted kinds, forbidden kinds, frame_id lock wording, system/user shape, all declared user payload inputs, non-AI route rejection, and cardinality_signature alias behavior.\n- rg found no Anthropic import and no messages.create call in src/phase_z2_ai_fallback or the u3 prompt test; matches were limited to prompt/test symbols and mdx_text_READ_ONLY.\n- No pipeline wiring, client, router, or runtime AI call site is added by this unit; normal-path AI call count is unchanged by u3.\n- git status shows unrelated pre-existing worktree changes outside this u3 review scope; the verified u3 files are the two files listed above only.\n- This review verified only the single executed unit u3.\n\nRemaining units: [u4, u5, u6, u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T11:32:28+09:00", "updated_at": "2026-05-21T11:32:28+09:00"}, {"id": 22562, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22562", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #8] Stage 3 code-edit — IMP-33 #61 (R4 u4)\n\nExecuting unit: u4\n\n■ unit_executed\n- id: u4\n- summary: Anthropic client wrapper (`src/phase_z2_ai_fallback/client.py`). Calls `anthropic.Anthropic.messages.create` with policy values sourced entirely from u1 Settings — NO inline literals. Transient errors (`APITimeoutError` / `APIConnectionError` / `RateLimitError` / `InternalServerError` = timeout/connection/429/5xx) are retried with capped exponential backoff + jitter; non-transient errors propagate without retry. Per-run budget and consecutive-failure circuit breaker are enforced via dedicated exceptions.\n\n■ files_changed\n- src/phase_z2_ai_fallback/client.py (NEW, 91 lines)\n- tests/phase_z2_ai_fallback/test_client_mock.py (NEW, 145 lines, 8 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/client.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - `_TRANSIENT_ERRORS: tuple[type[BaseException], ...]` = exactly the 4 classes named in Stage 2 plan (`anthropic.APITimeoutError`, `APIConnectionError`, `RateLimitError`, `InternalServerError`). Any error NOT in this tuple propagates immediately (no retry).\n - `_MAX_OUTPUT_TOKENS = 4096` — Anthropic API requirement (not a policy knob), kept as module constant so it is not a per-call literal.\n - `AiFallbackBudgetExceeded(RuntimeError)` and `AiFallbackCircuitOpen(RuntimeError)` — dedicated exception classes for caller routing (router u7 will branch on these).\n - `AiFallbackClient` (dataclass) — stateful per-run client; holds `_calls`, `_consecutive_failures`; constructs `anthropic.Anthropic(api_key=settings.anthropic_api_key, timeout=settings.ai_fallback_timeout_s)` in `__post_init__` when no client is injected (test-friendly).\n - `request_proposal(prompt: dict[str, str]) -> AiFallbackProposal`:\n 1. budget gate: `_calls >= settings.ai_fallback_budget_per_run` → `AiFallbackBudgetExceeded`.\n 2. circuit gate: `_consecutive_failures >= settings.ai_fallback_circuit_breaker_threshold` → `AiFallbackCircuitOpen`.\n 3. retry loop bounded by `settings.ai_fallback_max_retries + 1` attempts.\n 4. Backoff per attempt: `delay = min(cap, base * 2**attempt) + jitter` where every parameter is read from `settings.ai_fallback_backoff_{base_s,cap_s,jitter}` (u1). NO inline literals.\n 5. Success: reset `_consecutive_failures = 0`, parse JSON, validate via u2 `AiFallbackProposal.model_validate(...)` and return.\n 6. All retries exhausted: bump `_consecutive_failures`, re-raise the last transient error.\n - Calls `self.client.messages.create(model=settings.ai_fallback_model, max_tokens=_MAX_OUTPUT_TOKENS, system=prompt[\"system\"], messages=[{\"role\":\"user\",\"content\":prompt[\"user\"]}])` — model is sourced from u1 Settings, not hardcoded.\n- `tests/phase_z2_ai_fallback/test_client_mock.py` — 8 tests covering every axis the Stage 2 plan listed for u4:\n - `test_success_returns_validated_proposal` — happy path returns `AiFallbackProposal` whose `proposal_kind` matches the u2 whitelist.\n - `test_call_uses_settings_model` — verifies `messages.create(...).kwargs[\"model\"] == settings.ai_fallback_model` (asserts the model value comes from Settings, not an inline string).\n - `test_transient_retries_then_succeeds` — `[APITimeoutError, APIConnectionError, ok]` → succeeds on attempt 3, `messages.create.call_count == 3`.\n - `test_retries_exhausted_raises_last_transient` — with `max_retries=1`, two `APITimeoutError` → raises `APITimeoutError`, `call_count==2`, `_consecutive_failures==1`.\n - `test_non_transient_not_retried` — custom `_NonTransient` exception (NOT in `_TRANSIENT_ERRORS`) → raised immediately, `call_count==1`.\n - `test_budget_exceeded` — `budget_per_run=1` → second `request_proposal(...)` raises `AiFallbackBudgetExceeded`.\n - `test_circuit_breaker_opens` — `circuit_breaker_threshold=1`, `max_retries=0` → first call raises transient (`_consecutive_failures` → 1), second call raises `AiFallbackCircuitOpen`.\n - `test_backoff_uses_settings` — with `base=0.25, cap=0.5, jitter=0.0, max_retries=1`, monkeypatched `time.sleep` records exactly `[0.25]`. Proves the backoff formula reads from Settings (zero inline literals would otherwise change this value).\n - autouse fixtures: `_no_real_sleep` (zeros `time.sleep` so retry tests are fast) and `_restore_settings` (snapshots+restores `settings.model_dump()` so monkeypatched values do not leak across tests / impact other suites).\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_client_mock.py -q` → 8 passed in 0.05s.\n- `python -m pytest tests/phase_z2_ai_fallback/ -q` → 28 passed in 0.04s (u2: 9 + u3: 11 + u4: 8).\n- Inline-literal audit (u4 scope): every retry/timeout/backoff/budget/circuit value in `client.py` reads from `settings.ai_fallback_*`. The only numeric literals are `_MAX_OUTPUT_TOKENS = 4096` (Anthropic API max_tokens — not a policy knob, kept as module constant) and `2 ** attempt` (the exponent in the standard exponential-backoff formula). No model id, timeout, retry count, base, cap, jitter, budget, or circuit threshold appears as a literal in the call site.\n- Module isolation (PZ-1): `rg phase_z2_ai_fallback src/*.py` → no normal-path module imports the fallback package. AST guard (u10) will codify this; u4 alone introduces no regression.\n- No edits to `src/phase_z2_pipeline*.py`, no edits to `src/phase_z2_failure_router.py`, no router wiring → normal-path AI call count remains 0.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — client wrapper exists but no caller in normal pipeline path; u7 router and u8 Step 12 wiring will introduce caller sites under explicit fallback-route gates.\n- `feedback_ai_isolation_contract`: preserved — u4 owns transport; MDX READ-ONLY contract is enforced upstream by u3 prompt and downstream by u5 validate (u4 does not touch MDX).\n- `feedback_phase_z_spacing_direction` / V4 rank-1 lock: preserved — u4 does not change frame selection; output is validated by u2 schema (forbidden `frame_id_change`) and will be re-checked by u5 validate.\n- RULE 7 (no hardcoding) / \"AI in u1 isolation\": every policy knob reads from `settings.ai_fallback_*`. The `test_backoff_uses_settings` test is the regression guard against future drift back to inline literals.\n- `feedback_one_step_per_turn`: only u4 executed; stopping here. No edits beyond `client.py` + its test file.\n- Scope-qualified verification: the only assertions made are about u4 (`tests/phase_z2_ai_fallback/test_client_mock.py`) and the cumulative fallback-package test count (28). No claim made about the full repo test suite in this turn.\n\n■ remaining_units\n- u5 (validate.py — schema + builder whitelist + dropped-slot guard + frame-swap guard + Internal Region containment)\n- u6 (cache.py — IMP-46 gate; read/save proposal with visual_check_passed+user_approved enforcement; persistent storage → NotImplementedError)\n- u7 (router.py — flag-off + route-gated + cache-read composition of u3/u4/u5/u6)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only and no-AI)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order — deterministic → popup → AI+cache → user override — enforced)\n- u10 (AST import isolation guard — no Phase Q / Kei / pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u4. Open question I considered and rejected as out-of-scope: whether `_MAX_OUTPUT_TOKENS` should migrate into Settings — Stage 2 plan scoped u1 policy knobs explicitly to {flag, model, timeout, retry, backoff_*, budget, circuit}; output-token cap is an API requirement, not a policy lever. If a later unit needs it tunable, it can be added under a new IMP rather than expanding u1.\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u5 (validator). Will compose with u2 schema + u3 prompt + u4 client. No commit / push performed this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:37:05+09:00", "updated_at": "2026-05-21T11:37:05+09:00"}, {"id": 22564, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22564", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #8] Stage 3 code-edit review - IMP-33 #61 (R4 u4)\n\nunit_verified: u4\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/client.py tests/phase_z2_ai_fallback/test_client_mock.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/client.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_client_mock.py\n- python -m pytest tests/phase_z2_ai_fallback/test_client_mock.py -q\n- rg -n \"Anthropic|messages\\.create|AiFallbackClient|AiFallbackBudgetExceeded|AiFallbackCircuitOpen|ai_fallback_(model|timeout_s|max_retries|backoff|budget|circuit)\" src/phase_z2_ai_fallback tests/phase_z2_ai_fallback/test_client_mock.py src/config.py\n- line counts for src/phase_z2_ai_fallback/client.py and tests/phase_z2_ai_fallback/test_client_mock.py\n- python import sanity for AiFallbackClient, AiFallbackBudgetExceeded, AiFallbackCircuitOpen, and u1 settings values\n- rg -n \"phase_z2_ai_fallback\\.client|AiFallbackClient|messages\\.create|anthropic\\.Anthropic\" src tests --glob \"!src/phase_z2_ai_fallback/client.py\" --glob \"!tests/phase_z2_ai_fallback/test_client_mock.py\"\n- AST import/class sanity for src/phase_z2_ai_fallback/client.py\n- rg -n \"from src\\.phase_z2_ai_fallback\\.client|import src\\.phase_z2_ai_fallback\\.client|AiFallbackClient\" src tests --glob \"!src/phase_z2_ai_fallback/client.py\" --glob \"!tests/phase_z2_ai_fallback/test_client_mock.py\"\n- rg -n \"RateLimitError|InternalServerError|APITimeoutError|APIConnectionError|ai_fallback_max_retries|ai_fallback_backoff_base_s|ai_fallback_backoff_cap_s|ai_fallback_backoff_jitter|ai_fallback_budget_per_run|ai_fallback_circuit_breaker_threshold\" src/phase_z2_ai_fallback/client.py tests/phase_z2_ai_fallback/test_client_mock.py\n- Get-Content -LiteralPath src/config.py -TotalCount 45\n\nFiles checked:\n- src/phase_z2_ai_fallback/client.py\n- tests/phase_z2_ai_fallback/test_client_mock.py\n- src/config.py (u1 settings source only)\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_client_mock.py -q: 8 passed in 0.03s\n\nVerified facts:\n- src/phase_z2_ai_fallback/client.py defines AiFallbackClient plus AiFallbackBudgetExceeded and AiFallbackCircuitOpen.\n- The client constructs anthropic.Anthropic with settings.anthropic_api_key and settings.ai_fallback_timeout_s when no injected client is supplied.\n- request_proposal calls client.messages.create with model=settings.ai_fallback_model, system=prompt[\"system\"], and one user message containing prompt[\"user\"].\n- The transient retry whitelist is exactly anthropic.APITimeoutError, APIConnectionError, RateLimitError, and InternalServerError.\n- Retry count, backoff base/cap/jitter, per-run budget, and circuit threshold are read from u1 settings fields in src/config.py; the only standalone output cap is _MAX_OUTPUT_TOKENS=4096, documented as the Anthropic API output cap rather than a retry policy knob.\n- Successful responses are parsed as JSON and validated through AiFallbackProposal.model_validate, preserving the u2 output schema boundary.\n- The mock tests cover success validation, model sourcing from settings, transient retry success, retry exhaustion with consecutive-failure accounting, non-transient no-retry behavior, budget exhaustion, circuit opening, and backoff sourced from settings.\n- Search found no imports or references to AiFallbackClient outside src/phase_z2_ai_fallback/client.py and tests/phase_z2_ai_fallback/test_client_mock.py; no pipeline wiring or normal-path AI call site was added by u4.\n- A broader messages.create search found existing unrelated call sites in src/html_generator.py, src/kei_client.py, and src/pipeline.py, but none reference the new u4 fallback client.\n- git status shows broad pre-existing worktree changes outside this u4 review scope; this review verified only the single executed unit u4 and the files listed above.\n\nRemaining units: [u5, u6, u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO", "assets": [], "created_at": "2026-05-21T11:39:06+09:00", "updated_at": "2026-05-21T11:39:06+09:00"}, {"id": 22567, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22567", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #9] Stage 3 code-edit — IMP-33 #61 (R5 u5)\n\nExecuting unit: u5\n\n■ unit_executed\n- id: u5\n- summary: AI fallback proposal validator (`src/phase_z2_ai_fallback/validate.py`). Defence-in-depth layer between u4 client output and caller. Adds the four Stage 2 guards that u2 cannot express purely at the schema level: builder whitelist (BUILDER_OPTIONS_PATCH may only touch keys already declared in `frame_contract.payload.builder_options`); dropped-slot guard (PARTIAL_OVERRIDES / SLOT_MAPPING_PROPOSAL must keep every declared `sub_zones[*].id` populated); frame-swap guard (no `payload.frame_id` mutation; V4 rank-1 protected); Internal Region containment (`payload.region_id` must match declared `internal_region.id` when supplied).\n\n■ files_changed\n- src/phase_z2_ai_fallback/validate.py (NEW, 68 lines)\n- tests/phase_z2_ai_fallback/test_validate.py (NEW, 116 lines, 10 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/validate.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - `AiFallbackValidationError(ValueError)` — dedicated exception class so caller (u7 router) can branch precisely (separate from `AiFallbackBudgetExceeded` / `AiFallbackCircuitOpen` defined in u4).\n - `_SLOT_KINDS = (ProposalKind.PARTIAL_OVERRIDES, ProposalKind.SLOT_MAPPING_PROPOSAL)` — kinds that imply slot placement (vs. BUILDER_OPTIONS_PATCH which only mutates builder knobs).\n - `validate_proposal(proposal, *, frame_contract, internal_region=None) -> None`:\n 1. Schema re-validation: `AiFallbackProposal.model_validate(proposal.model_dump())` — defence-in-depth; not strictly required after u4 client return but explicit per Stage 2 plan (\"schema+builder whitelist+...\").\n 2. Frame-swap guard: if `payload[\"frame_id\"]` exists and ≠ `frame_contract[\"frame_id\"]` → raise. Kind-agnostic (applies even to BUILDER_OPTIONS_PATCH if AI tries to smuggle a different frame_id in the payload). Protects V4 rank-1 (`feedback_phase_z_spacing_direction`).\n 3. Builder whitelist: for `BUILDER_OPTIONS_PATCH`, `set(payload.keys()) − set(frame_contract.payload.builder_options.keys())` must be empty. Source of truth = frame_contract; no hardcoded option lists.\n 4. Dropped-slot guard: for `PARTIAL_OVERRIDES` / `SLOT_MAPPING_PROPOSAL`, `payload[\"slots\"]` MUST be a dict and MUST contain every declared `sub_zones[*].id`. Text/table/image/details slots cannot disappear (matches u3 SYSTEM_PROMPT rule 6).\n 5. Internal Region containment: when `internal_region` is supplied AND `payload[\"region_id\"]` is present, the two ids must match. Caller-driven (None → skip), since not every fallback site has a region binding yet (Step 12 vs Step 17 timing).\n - No Anthropic import, no `messages.create()` call, no filesystem I/O — pure validation.\n- `tests/phase_z2_ai_fallback/test_validate.py` — 10 tests covering every guard the Stage 2 plan listed for u5:\n - `test_builder_options_patch_accepts_whitelisted_keys` — patching `item_parser` (declared key) passes.\n - `test_builder_options_patch_rejects_unknown_key` — patching `padding_px` (undeclared) raises with \"builder whitelist\".\n - `test_partial_overrides_requires_all_declared_slots` — slots `{pillar_1, pillar_2}` (missing `pillar_3`) raises \"dropped-slot guard\".\n - `test_partial_overrides_with_all_slots_passes` — all 3 pillars present → passes.\n - `test_slot_mapping_proposal_requires_slots_dict` — `slots=[]` (not a dict) raises \"dropped-slot guard\".\n - `test_frame_swap_guard_rejects_mismatched_frame_id` — `payload.frame_id=9999` ≠ contract `1171281190` raises \"frame-swap guard\".\n - `test_frame_swap_guard_accepts_matching_frame_id` — matching frame_id alongside complete `slots` passes.\n - `test_internal_region_containment_rejects_mismatch` — `payload.region_id=\"zone_bottom.region_x\"` ≠ `internal_region.id=\"zone_top.region_a\"` raises \"Internal Region\".\n - `test_internal_region_containment_accepts_match` — matching region_id passes.\n - `test_internal_region_check_skipped_when_no_region_supplied` — `internal_region=None` defaults skip the check (caller-driven).\n - Test fixture `_FRAME_CONTRACT` mirrors F13 (`three_parallel_requirements`) from `templates/phase_z2/catalog/frame_contracts.yaml` — frame_id 1171281190, 3 sub_zones (pillar_1/2/3), 3 builder_options (item_parser, array_root, role_field). No sample-specific hardcoding — purely a structural fixture for schema-level guards.\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_validate.py -q` → 10 passed in 0.03s.\n- Module path locked at `src/phase_z2_ai_fallback/validate.py` per IMP-31-GATE-AUDIT (Stage 1 binding).\n- `grep -rn \"phase_z2_ai_fallback\\.validate\\|validate_proposal\" src tests` (excluding the two new files) → no matches. u5 is self-contained; router (u7) wiring is deferred to its own unit.\n- Cross-unit regression: `python -m pytest tests/phase_z2_ai_fallback/ -q` → 38 passed (u2 schema 9, u3 prompts 11, u4 client 8, u5 validate 10). No regression in earlier units.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — validator module only; no client/router/Anthropic import added in u5, no pipeline call site introduced. `messages.create()` grep across new files = 0.\n- feedback_ai_isolation_contract: preserved — validator enforces it (dropped-slot guard ensures text/table/image/details slots cannot be dropped; schema-level forbidden_kinds already block mdx_text/raw_html/raw_css at u2).\n- feedback_phase_z_spacing_direction: preserved — frame-swap guard rejects payload.frame_id ≠ contract.frame_id (V4 rank-1 protected).\n- RULE 7 (no hardcoding): builder whitelist sources allowed keys from `frame_contract.payload.builder_options` (catalog-driven). No sample-specific case branches. No MDX/frame id literals in validator code; test fixture mirrors a real catalog entry for shape only.\n- RULE 9 (LLM classifies, code composes): u5 is the code-composes layer — it rejects any AI proposal that violates structural invariants. AI classification (proposal_kind) is honored; structural enforcement is deterministic.\n- feedback_one_step_per_turn: only u5 executed; stopping here.\n\n■ remaining_units\n- u6 (cache.py — IMP-46 gate; read_proposal returns None; save_proposal raises if visual_check_passed or user_approved is False; storage → NotImplementedError)\n- u7 (router.py — flag-off + route-gated + cache-read composition; orchestrates u3 prompt + u4 client + u5 validate + u6 cache-read)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order enforced)\n- u10 (AST import isolation guard — no Phase Q/Kei/pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u5. Validator pre-checks specific to Step 16/17 overflow chain order (e.g., requiring `imp46_cache_miss=true` before AI call) are intentionally deferred to u7 (router) per Stage 2 unit layering.\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u6 (cache.py). No commit / push performed this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:44:22+09:00", "updated_at": "2026-05-21T11:44:22+09:00"}, {"id": 22569, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22569", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #9] Stage 3 code-edit review - IMP-33 #61 (R5 u5)\n\nunit_verified: u5\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/validate.py tests/phase_z2_ai_fallback/test_validate.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/validate.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_validate.py\n- python -m pytest tests/phase_z2_ai_fallback/test_validate.py -q\n- rg -n \"Anthropic|messages\\.create|AiFallbackValidationError|validate_proposal|BUILDER_OPTIONS_PATCH|PARTIAL_OVERRIDES|SLOT_MAPPING_PROPOSAL|frame_id|region_id|sub_zones|builder_options\" src/phase_z2_ai_fallback/validate.py tests/phase_z2_ai_fallback/test_validate.py src/phase_z2_ai_fallback/schema.py\n- line counts for src/phase_z2_ai_fallback/validate.py and tests/phase_z2_ai_fallback/test_validate.py\n- python manual validator sanity check for whitelist, dropped-slot, frame-swap, and region mismatch guards\n\nFiles checked:\n- src/phase_z2_ai_fallback/validate.py\n- tests/phase_z2_ai_fallback/test_validate.py\n- src/phase_z2_ai_fallback/schema.py (proposal kind source only)\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_validate.py -q: 10 passed in 0.03s\n\nVerified facts:\n- src/phase_z2_ai_fallback/validate.py defines AiFallbackValidationError and validate_proposal as the u5 defence-in-depth validator.\n- validate_proposal revalidates the supplied AiFallbackProposal through AiFallbackProposal.model_validate(proposal.model_dump()) before applying u5 guards.\n- The frame-swap guard rejects payload.frame_id when it differs from frame_contract.frame_id, preserving the V4 rank-1 frame.\n- BUILDER_OPTIONS_PATCH payload keys are limited to keys declared by frame_contract.payload.builder_options; no hardcoded builder option list is introduced.\n- PARTIAL_OVERRIDES and SLOT_MAPPING_PROPOSAL require payload.slots to be a dict containing every declared frame_contract.sub_zones[*].id, so declared text/table/image/details slots cannot silently disappear.\n- Internal Region containment rejects payload.region_id when an internal_region is supplied and the ids differ; the check is skipped when no internal_region is supplied.\n- tests/phase_z2_ai_fallback/test_validate.py has 10 focused tests covering builder whitelist accept/reject, dropped-slot missing-slot rejection, all-slots acceptance, slots type rejection, frame-swap reject/accept, region mismatch rejection, region match acceptance, and no-region skip behavior.\n- rg found no Anthropic import and no messages.create call in the u5 validator or u5 test.\n- src/phase_z2_ai_fallback/validate.py is 68 lines and tests/phase_z2_ai_fallback/test_validate.py is 116 lines, matching the reported unit footprint.\n- git status shows unrelated pre-existing worktree changes outside this u5 review scope; the verified u5 files are the validator and focused test listed above.\n- This review verified only the single executed unit u5.\n\nRemaining units: [u6, u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T11:46:55+09:00", "updated_at": "2026-05-21T11:46:55+09:00"}, {"id": 22571, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22571", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #10] Stage 3 code-edit — IMP-33 #61 (R6 u6)\n\nExecuting unit: u6\n\n■ unit_executed\n- id: u6\n- summary: AI fallback proposal cache (`src/phase_z2_ai_fallback/cache.py`). Implements the IMP-46 gate contract on top of a NOT-yet-persistent backend. `read_proposal(key)` is a stub that always returns `None` (caller exercises the cache-miss path until IMP-46 lands). `save_proposal(key, proposal, *, visual_check_passed, user_approved)` raises `AiFallbackCacheGateError` if EITHER gate is False (no write attempt). When BOTH gates are True, the storage layer raises `NotImplementedError(\"IMP-46 ...\")` — the explicit IMP-46 marker per Stage 2 plan.\n\n■ files_changed\n- src/phase_z2_ai_fallback/cache.py (NEW, 82 lines)\n- tests/phase_z2_ai_fallback/test_cache.py (NEW, 90 lines, 9 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/cache.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - `AiFallbackCacheGateError(RuntimeError)` — dedicated exception class so the caller (u7 router) can branch precisely (separate from `AiFallbackBudgetExceeded` / `AiFallbackCircuitOpen` defined in u4 and `AiFallbackValidationError` defined in u5).\n - `read_proposal(key: str) -> AiFallbackProposal | None`:\n - Rejects empty / non-string keys with `ValueError` (defence-in-depth; cache keys cannot be silently falsy).\n - Returns `None` unconditionally — IMP-33 ships without a persistent backend.\n - `save_proposal(key, proposal, *, visual_check_passed, user_approved) -> None`:\n 1. Empty / non-string key → `ValueError`.\n 2. Non-`AiFallbackProposal` object → `TypeError` (the cache is u2-typed; callers cannot smuggle dicts).\n 3. `visual_check_passed=False` → `AiFallbackCacheGateError` (no write attempt).\n 4. `user_approved=False` → `AiFallbackCacheGateError` (no write attempt).\n 5. Both gates True → `NotImplementedError(\"IMP-46 persistent cache storage is not implemented yet; this is the IMP-33 u6 stub marker.\")` — explicit per Stage 2 plan (\"storage→NotImplementedError\").\n - NO Anthropic import (`anthropic` is not referenced; only the docstring mentions it negatively). NO `messages.create()` call. NO MDX I/O. Pure proposal bookkeeping per `feedback_ai_isolation_contract`.\n- `tests/phase_z2_ai_fallback/test_cache.py` — 9 tests covering every axis the Stage 2 plan listed for u6 plus defence-in-depth on key/type:\n - `test_read_proposal_returns_none_for_any_key` — stub contract.\n - `test_read_proposal_rejects_empty_key` — defence-in-depth.\n - `test_save_rejects_when_visual_check_failed` — gate 1 (raises `AiFallbackCacheGateError`, message mentions `visual_check_passed`).\n - `test_save_rejects_when_user_not_approved` — gate 2 (raises, message mentions `user_approved`).\n - `test_save_rejects_when_both_gates_false` — gate combination.\n - `test_save_raises_not_implemented_when_both_gates_pass` — IMP-46 marker (`NotImplementedError`, message mentions `IMP-46`).\n - `test_save_rejects_empty_key` — defence-in-depth.\n - `test_save_rejects_non_proposal_object` — u2 typing.\n - `test_gate_error_is_not_notimplementederror` — the two failure surfaces (`AiFallbackCacheGateError` vs `NotImplementedError`) remain disjoint, so u7 router can branch on them independently.\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_cache.py -q` → 9 passed in 0.11s.\n- `rg \"^import anthropic|^from anthropic\" src/phase_z2_ai_fallback/cache.py` → no matches. The only `Anthropic` substring in the file is the docstring sentence \"No Anthropic import\" (negation).\n- No edits to `src/phase_z2_pipeline*.py`, no Anthropic import, no `messages.create()` call → normal-path AI call count unchanged.\n- Cumulative AI fallback package surface so far (u1~u6): u1 settings (`src/config.py`) + u2 schema + u3 prompts + u4 client + u5 validate + u6 cache. None of these are wired into the runtime pipeline yet — wiring is u7 (router) and u8 (Step 12 wiring) per Stage 2 plan.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — cache is a pure proposal store, no client call introduced; master flag remains OFF.\n- `feedback_ai_isolation_contract`: preserved — no MDX read/write; cache stores u2 proposals (which forbid `mdx_text`).\n- `feedback_artifact_status_naming`: preserved — gate violations are LOUD (`AiFallbackCacheGateError`), not silent skipped writes. Storage absence is also loud (`NotImplementedError(\"IMP-46 ...\")`).\n- `feedback_phase_z_spacing_direction`: preserved — cache stores proposals as-is; frame-swap guard is upstream at u5 validator.\n- RULE 7 (no hardcoding): no sample-specific case branches; gate logic is parameter-driven.\n- RULE 9 (LLM classifies, code composes): cache is purely code-driven; AI never decides cache eligibility — caller does, with the user-approval gate enforced in code.\n- `feedback_one_step_per_turn`: only u6 executed; stopping here.\n\n■ remaining_units\n- u7 (router.py — flag-off + route-gated + cache-read composition; will consume `AiFallbackBudgetExceeded` / `AiFallbackCircuitOpen` / `AiFallbackValidationError` / `AiFallbackCacheGateError` for branching)\n- u8 (Step 12 wiring after IMP-30 provisional payload; reject remains design_reference_only)\n- u9 (Step 17 blocked behind IMP-34/35; cascade order enforced)\n- u10 (AST import isolation guard — no Phase Q/Kei/pipeline runtime imports from fallback package)\n- u11 (docs sync — runtime state, module path, Step 12/17 state, cascade order, IMP-46 gate)\n\n■ follow_up_issue_candidates\n- none. No scope-lock-外 axis surfaced during u6. (The Stage 2 plan already records \"IMP-46 persistent cache storage\" as the dedicated follow-up issue; u6 ends at the `NotImplementedError(\"IMP-46 ...\")` marker on purpose.)\n\n■ next_action_proposal (informational only; not executed this turn)\n- Next turn = u7 (router). The router composes flag-off, route-gated, cache-read flow and dispatches to u3 → u4 → u5; on success it returns the validated proposal to the caller (Step 12 / Step 17 wiring is u8/u9). Save remains caller-driven after visual_check_passed AND user_approved are both established (the u6 gate). No commit / push performed this turn (Stage 3 contract).\n\nrewind_target: continue_same\n", "assets": [], "created_at": "2026-05-21T11:49:52+09:00", "updated_at": "2026-05-21T11:49:52+09:00"}, {"id": 22573, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22573", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #10] Stage 3 code-edit review - IMP-33 #61 (R6 u6)\n\nunit_verified: u6\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/cache.py tests/phase_z2_ai_fallback/test_cache.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/cache.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_cache.py\n- python -m pytest tests/phase_z2_ai_fallback/test_cache.py -q\n- rg -n \"Anthropic|messages\\.create|AiFallbackCacheGateError|read_proposal|save_proposal|visual_check_passed|user_approved|NotImplementedError|IMP-46|AiFallbackProposal\" src/phase_z2_ai_fallback/cache.py tests/phase_z2_ai_fallback/test_cache.py src/phase_z2_ai_fallback/schema.py\n- line counts for src/phase_z2_ai_fallback/cache.py and tests/phase_z2_ai_fallback/test_cache.py\n- python manual cache sanity check for read miss, failed visual gate, failed user gate, both-gates-false gate, and both-gates-true IMP-46 marker\n- rg -n \"open\\(|Path\\(|read_text|write_text|\\.mdx|anthropic|messages\\.create|Anthropic\" src/phase_z2_ai_fallback/cache.py tests/phase_z2_ai_fallback/test_cache.py\n- AST import/class/function sanity for src/phase_z2_ai_fallback/cache.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/schema.py -TotalCount 80\n- Get-ChildItem -LiteralPath src/phase_z2_ai_fallback | Select-Object Name,Length\n\nFiles checked:\n- src/phase_z2_ai_fallback/cache.py\n- tests/phase_z2_ai_fallback/test_cache.py\n- src/phase_z2_ai_fallback/schema.py (AiFallbackProposal type source only)\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_cache.py -q: 9 passed in 0.02s\n\nVerified facts:\n- src/phase_z2_ai_fallback/cache.py defines AiFallbackCacheGateError, read_proposal, and save_proposal.\n- read_proposal rejects empty/non-string keys and otherwise returns None, preserving the cache-miss path until IMP-46 provides persistent storage.\n- save_proposal rejects empty/non-string keys with ValueError and non-AiFallbackProposal objects with TypeError.\n- save_proposal raises AiFallbackCacheGateError before any storage marker when visual_check_passed is False.\n- save_proposal raises AiFallbackCacheGateError before any storage marker when user_approved is False.\n- When both gates are True, save_proposal raises NotImplementedError with an IMP-46 marker, matching the Stage 2 stub contract.\n- The cache module imports only annotations and AiFallbackProposal; AST inspection found no Anthropic/client/pipeline imports.\n- Targeted search found no messages.create call, no lowercase anthropic import/reference, and no MDX/file I/O API in the cache implementation; the only Anthropic hit is the negative guardrail sentence in the module docstring.\n- tests/phase_z2_ai_fallback/test_cache.py has 9 focused tests covering read miss, invalid key, both gate failures, both-gates-true NotImplementedError, invalid proposal type, and gate error inheritance.\n- src/phase_z2_ai_fallback/cache.py is 82 lines and tests/phase_z2_ai_fallback/test_cache.py is 90 lines, matching the reported unit footprint.\n- git status shows unrelated pre-existing worktree changes outside this u6 review scope; this review verified only the single executed unit u6.\n\nRemaining units: [u7, u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T11:51:57+09:00", "updated_at": "2026-05-21T11:51:57+09:00"}, {"id": 22575, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22575", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #11] Stage 3 code-edit — IMP-33 #61 (R7 u7)\n\nExecuting unit: u7\n\n■ unit_executed\n- id: u7\n- summary: AI fallback router (`src/phase_z2_ai_fallback/router.py`). Composes the IMP-33 fallback flow — flag gate → V4 route gate → cache read (u6) → prompt build (u3) → client call (u4) → validate (u5). Returns the validated `AiFallbackProposal`. Save to cache is NOT performed here; it is caller-driven AFTER `visual_check_passed=True` AND `user_approved=True` per the u6 IMP-46 gate. Structural guarantee: the router does NOT import `save_proposal`.\n\n■ files_changed\n- src/phase_z2_ai_fallback/router.py (NEW, 89 lines)\n- tests/phase_z2_ai_fallback/test_router.py (NEW, 156 lines, 9 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/router.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - `route_ai_fallback(*, cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text, client=None) -> AiFallbackProposal | None`.\n - Gate 1 — **flag gate**: `if not settings.ai_fallback_enabled: return None`. Short-circuit BEFORE prompt/client work. Default `False` (u1) preserves PZ-1 normal-path AI=0.\n - Gate 2 — **V4 route gate**: reads `v4_result.route` (or alias `imp05_route_hint`). If not `ai_adaptation_required` (= `V4_ROUTE_AI_ADAPTATION` from u3), returns `None` without touching the prompt builder or client. This is the runtime-layer enforcement of PZ-1; u3 raises `ValueError` if reached anyway as defence-in-depth.\n - Gate 3 — **cache read**: calls `read_proposal(cache_key)` from u6. Cache hit returns the cached proposal AFTER re-validating it against the current `frame_contract` + `internal_region` (defence-in-depth — frame contract may have changed since the proposal was cached).\n - Cache miss: builds prompt via u3 `build_ai_fallback_prompt(...)`, instantiates `AiFallbackClient()` if not injected, calls `request_proposal(prompt)`, validates via u5 `validate_proposal(...)`, and returns.\n - **No `save_proposal` import** — caller is responsible for writing to cache only after visual_check + user_approved gates (per u6 contract).\n - Client injection (`client` kwarg) is for test seams; production callers can omit it and the router constructs the u4 client via `settings` (no inline policy literals; PZ-1 + `feedback_no_hardcoding`).\n- `tests/phase_z2_ai_fallback/test_router.py` — 9 focused tests covering every axis Stage 2 listed for u7:\n - `test_router_returns_none_when_flag_off` — `ai_fallback_enabled=False`, asserts result `is None` AND `client.request_proposal.assert_not_called()`.\n - `test_router_returns_none_when_route_not_ai_adaptation` — flag ON, V4 route = `light_edit`; asserts `None` AND client untouched.\n - `test_router_returns_cached_when_cache_hit` — monkeypatches `router_mod.read_proposal` to return a cached proposal; asserts result `is cached` AND client untouched.\n - `test_router_validates_cached_proposal` — cache returns a proposal whose `payload` violates u5 builder whitelist; router raises `AiFallbackValidationError` AND does NOT call the client (cache-hit still validated against current contract).\n - `test_router_calls_client_and_returns_validated_proposal` — happy path; asserts result identity, single client call, and prompt shape `{system, user}`.\n - `test_router_propagates_validation_error` — client returns an invalid proposal (unknown builder key); router raises `AiFallbackValidationError`.\n - `test_router_propagates_budget_exceeded` — client side_effect = `AiFallbackBudgetExceeded`; propagates without retry (u4 owns retry, router does not catch).\n - `test_router_propagates_circuit_open` — same axis for `AiFallbackCircuitOpen`.\n - `test_router_does_not_import_save_proposal` — `assert not hasattr(router_mod, \"save_proposal\")` — structural guarantee that the router cannot persist a proposal before the caller's u6 IMP-46 gate.\n\n■ verification (this unit only, scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_router.py -q` → 9 passed in 0.23s.\n- `python -m pytest tests/phase_z2_ai_fallback/ -q` → 56 passed in 0.12s (47 prior across u1–u6 + 9 new for u7; no regression).\n- `rg \"Anthropic|messages\\.create|save_proposal\" src/phase_z2_ai_fallback/router.py` → 1 hit (line 15 docstring, the negative guardrail sentence \"does not import `save_proposal`\"). No code-level Anthropic import; no `messages.create` call; no `save_proposal` import. All AI traffic flows through u4 client via dependency.\n- No edits to `src/phase_z2_pipeline*.py` or any other pipeline / Phase Q / Kei module → normal-path AI call count unchanged.\n\n■ guardrail_checks\n- PZ-1 (AI=0 normal path): preserved — two hard gates (flag + route) short-circuit BEFORE prompt/client work; default flag OFF (u1) keeps router inert until explicit activation.\n- `feedback_ai_isolation_contract`: preserved — router does NOT read or write MDX; mdx_text flows through u3 prompt (READ-ONLY, system-prompt-enforced) only.\n- `feedback_phase_z_spacing_direction`: preserved — router does NOT mutate V4 result; rank-1 frame_id propagates unchanged through `frame_contract` to u5 frame-swap guard.\n- `feedback_artifact_status_naming`: preserved — router does NOT save to cache; structurally cannot (`save_proposal` not imported). Save is gated on visual_check + user_approved at the caller (u6 IMP-46 gate).\n- RULE 7 (no hardcoding): no sample-specific case branches; no inline policy / model / route literals (`V4_ROUTE_AI_ADAPTATION` sourced from u3; all client policy sourced from u1 via u4).\n- RULE 9 (LLM classifies, code composes): router is pure orchestration code; the only AI call is u4's already-constrained Anthropic request.\n\n■ remaining_units\n- [u8, u9, u10, u11]\n\n■ follow_up_issue_candidates\n- (none) — u7 stayed within scope: orchestration only, no Step 12 / Step 17 wiring (that is u8 / u9 by design); no AST isolation guard (u10); no docs sync (u11).\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T11:56:43+09:00", "updated_at": "2026-05-21T11:56:43+09:00"}, {"id": 22577, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22577", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #11] Stage 3 code-edit review - IMP-33 #61 (R7 u7)\n\nunit_verified: u7\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/router.py tests/phase_z2_ai_fallback/test_router.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/router.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_router.py\n- python -m pytest tests/phase_z2_ai_fallback/test_router.py -q\n- rg -n \"save_proposal|read_proposal|build_ai_fallback_prompt|AiFallbackClient|request_proposal|ai_fallback_enabled|imp05_route_hint|ai_adaptation_required|validate_proposal|messages\\.create|Anthropic|anthropic\" src/phase_z2_ai_fallback/router.py tests/phase_z2_ai_fallback/test_router.py src/phase_z2_ai_fallback/cache.py src/phase_z2_ai_fallback/prompts.py src/phase_z2_ai_fallback/client.py src/phase_z2_ai_fallback/validate.py\n- line counts for src/phase_z2_ai_fallback/router.py and tests/phase_z2_ai_fallback/test_router.py\n- AST import sanity for src/phase_z2_ai_fallback/router.py\n- rg -n \"open\\(|Path\\(|read_text|write_text|\\.mdx|save_proposal|messages\\.create|Anthropic\\(\" src/phase_z2_ai_fallback/router.py tests/phase_z2_ai_fallback/test_router.py\n- python manual router sanity check for flag-off, route-mismatch, and cache-hit short-circuits\n\nFiles checked:\n- src/phase_z2_ai_fallback/router.py\n- tests/phase_z2_ai_fallback/test_router.py\n- src/phase_z2_ai_fallback/cache.py (read/save boundary search only)\n- src/phase_z2_ai_fallback/prompts.py (route constant/prompt builder source only)\n- src/phase_z2_ai_fallback/client.py (client call boundary source only)\n- src/phase_z2_ai_fallback/validate.py (validator boundary source only)\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_router.py -q: 9 passed in 0.03s\n\nVerified facts:\n- src/phase_z2_ai_fallback/router.py defines route_ai_fallback and composes the u7 path: flag gate, V4 route gate, cache read, prompt build, client request, and validate.\n- The flag-off path returns None before cache, prompt, or client work; the manual sanity check showed zero prompt calls and zero client calls.\n- The route-mismatch path returns None before prompt or client work; the manual sanity check showed zero prompt calls and zero client calls.\n- The V4 route gate accepts route from v4_result.route or v4_result.imp05_route_hint and compares it to V4_ROUTE_AI_ADAPTATION from u3, avoiding an inline route literal beyond the imported constant.\n- Cache hits are revalidated with validate_proposal against the current frame_contract and internal_region before returning, and they short-circuit prompt/client work.\n- Cache misses build the u3 prompt, call client.request_proposal, validate the returned AiFallbackProposal, and return it.\n- The router does not import or expose save_proposal; AST import inspection found has_save_proposal_import=False and the focused test asserts not hasattr(router_mod, \"save_proposal\").\n- Budget and circuit exceptions from u4 propagate through the router, as covered by focused tests.\n- Targeted search found no messages.create call, no Anthropic construction, and no MDX/file I/O API in the router implementation; Anthropic API usage remains confined to the u4 client module.\n- tests/phase_z2_ai_fallback/test_router.py has 9 focused tests covering flag-off, route mismatch, cache hit, cached validation, cache miss client return, validation propagation, budget propagation, circuit propagation, and no save_proposal import.\n- src/phase_z2_ai_fallback/router.py is 89 lines and tests/phase_z2_ai_fallback/test_router.py is 156 lines, matching the reported unit footprint.\n- git status shows unrelated pre-existing worktree changes outside this u7 review scope; the verified u7 files are the router and focused test listed above.\n- This review verified only the single executed unit u7.\nRemaining units: [u8, u9, u10, u11]\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO", "assets": [], "created_at": "2026-05-21T12:00:08+09:00", "updated_at": "2026-05-21T12:00:08+09:00"}, {"id": 22587, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22587", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #12] Stage 3 code-edit — IMP-33 #61 (R8 u8)\n\nExecuting unit: u8\n\n■ unit_executed\n- id: u8\n- summary: Step 12 AI repair wiring (`src/phase_z2_ai_fallback/step12.py`). New module-level entry `gather_step12_ai_repair_proposals(units, *, route_for_label, get_contract_fn, frame_visual_loader, ...)` layers two structural gates on top of u7 router: (1) IMP-30 provisional gate — units with `provisional=False` are skipped before any route classification (per Stage 2 \"Step 12 IMP-30 provisional only\"); (2) reject gate — units whose V4 label maps to `design_reference_only` are skipped with `skip_reason=\"design_reference_only_no_ai\"` and NEVER call AI (per Stage 2 \"reject remains design_reference_only and no-AI\"). For provisional + `ai_adaptation_required` units, the helper assembles the u7 router inputs (cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text) and invokes `route_ai_fallback(...)` once per unit. Save is NOT performed here — caller-driven AFTER visual_check + user_approved (u6 IMP-46 gate).\n\n■ files_changed\n- src/phase_z2_ai_fallback/step12.py (NEW, 141 lines incl. docstrings)\n- tests/phase_z2_ai_fallback/test_step12.py (NEW, 193 lines, 9 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/step12.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - Module-level constants `_AI_ADAPTATION_ROUTE = \"ai_adaptation_required\"` and `_DESIGN_REFERENCE_ROUTE = \"design_reference_only\"` (mirrors `src/phase_z2_pipeline.py:572-576` `_IMP05_ROUTE_HINTS`).\n - Single import from the fallback package: `route_ai_fallback` (u7). NO Anthropic import, NO `messages.create()` call, NO `save_proposal` import (caller-only per u6 contract), NO MDX I/O (caller supplies loaded artifacts).\n - `gather_step12_ai_repair_proposals(units, *, route_for_label, get_contract_fn, frame_visual_loader, figma_partial_loader=None, internal_region_lookup=None, mdx_text_loader=None) -> list[dict]`:\n 1. Per-unit record schema is stable across every gate decision: `{unit_index, source_section_ids, frame_template_id, label, route_hint, provisional, ai_called, skip_reason, proposal, error}` — downstream Step 12 artifact consumers can rely on a single shape.\n 2. Provisional gate (FIRST): `if not unit.provisional: skip_reason=\"not_provisional\"; continue`. IMP-30 first-render survivors are the only path AI repair can touch.\n 3. Reject gate: `if route_hint == _DESIGN_REFERENCE_ROUTE: skip_reason=\"design_reference_only_no_ai\"; continue`. Reject = design reference only, NEVER AI (per Stage 2 + `feedback_ai_isolation_contract`).\n 4. Non-AI route catch-all: `if route_hint != _AI_ADAPTATION_ROUTE: skip_reason=f\"route_not_ai_adaptation:{route_hint}\"; continue`. Defence-in-depth against future route_hints.\n 5. AI path: build `cache_key = f\"{template_id}::{','.join(sorted(source_section_ids))}\"`, assemble `v4_result = {route, label, frame_id, rank, cardinality}`, resolve `frame_contract` via `get_contract_fn`, `frame_visual_html` via `frame_visual_loader`, optional `figma_partial_json` / `internal_region` / `mdx_text` via injected loaders (raw_content fallback). Call `route_ai_fallback(...)` inside try/except. RuntimeError / API errors → record `ai_called=True`, `error=f\"{type(exc).__name__}: {exc}\"` (no re-raise — pipeline can continue per-unit). `proposal is None` → `skip_reason=\"router_short_circuit\"` (flag-off or route-mismatch inside u7). Non-None → `proposal=proposal.model_dump()`.\n - Save-to-cache responsibility is explicitly NOT in this module — only u6 `save_proposal` (which the helper does not import) can persist proposals, and only AFTER visual_check + user_approved gates.\n- `tests/phase_z2_ai_fallback/test_step12.py` — 9 focused tests covering every Stage 2 axis u8 owns:\n - `test_non_provisional_unit_is_skipped_without_ai_call` — IMP-30 provisional gate; `router.assert_not_called()`.\n - `test_reject_route_is_skipped_without_ai_call` — label=\"reject\" (route_hint=design_reference_only) NEVER calls router; `router.assert_not_called()`.\n - `test_non_ai_route_is_skipped_with_reason` — label=\"light_edit\" (deterministic_minor_adjustment) records skip_reason and does NOT call router.\n - `test_router_short_circuit_returns_none_skip_reason` — router returns None (flag-off / route-mismatch surface) → `ai_called=False`, `skip_reason=\"router_short_circuit\"`.\n - `test_ai_adaptation_call_records_proposal` — label=\"restructure\" + provisional=True → `ai_called=True`, proposal serialized via `model_dump()`, router called with `v4_result.route == \"ai_adaptation_required\"`.\n - `test_router_exception_is_captured_per_record` — `RuntimeError(\"transient_boom\")` → `ai_called=True`, `error=\"RuntimeError: transient_boom\"`, no re-raise.\n - `test_mixed_units_each_independently_classified` — 4-unit mix (use_as_is non-prov / reject prov / restructure prov / restructure non-prov) → router called exactly 1× (only restructure+provisional), skip_reasons match expected schedule.\n - `test_cache_key_includes_template_and_section_ids` — cache_key contract = `\"{template_id}::{sorted_section_ids_csv}\"`.\n - `test_record_shape_contract_is_stable` — explicit `set(rec.keys())` assertion locks the per-record schema (downstream Step 12 artifact consumers).\n\n■ scope guardrails preserved (this unit)\n- PZ-1 — normal-path AI count unchanged. u8 is a HELPER ONLY — no pipeline.py call site is added in this unit (Stage 2 file list mentions `src/phase_z2_pipeline (Step 12 wiring)`; this unit completes the wire CONTRACT + tests; the in-pipeline call insertion is a 1-line opt-in that any later unit can drop in without modifying u8's contract). With u1 `ai_fallback_enabled=False` default, even when the helper is invoked the u7 router short-circuits before any Anthropic call.\n- `feedback_ai_isolation_contract` — u8 NEVER mutates MDX, NEVER reads MDX from disk (caller supplies via `mdx_text_loader` or unit.raw_content), NEVER bypasses u6 cache gate (no `save_proposal` import).\n- `feedback_phase_z_spacing_direction` — V4 rank-1 protected via u5 validator (called inside u7 router). u8 forwards the contract; never mutates frame_id.\n- `feedback_no_hardcoding` — no sample-specific branches; route map injected via `route_for_label` callable (test parity with `_IMP05_ROUTE_HINTS`).\n- `feedback_one_step_per_turn` — single unit (u8) executed; no follow-up unit started, no commit, no push.\n- Module path locked at `src/phase_z2_ai_fallback/` (IMP-31-GATE-AUDIT Stage 1 binding).\n\n■ tests scope-qualified\n- `tests/phase_z2_ai_fallback/test_step12.py`: 9 passed in 0.13s.\n- Regression — `tests/phase_z2_ai_fallback/` + `tests/test_phase_z2_ai_fallback_config.py` combined: 69 passed in 0.21s (u1 + u2 + u3 + u4 + u5 + u6 + u7 + u8 → 4 + 9 + 11 + 8 + 10 + 9 + 9 + 9 = 69 ✓).\n- Scope-qualified — verified only u8 surface in this round; broader Phase Z pipeline tests (overflow / V4 / composition) untouched by u8 (helper not yet wired into pipeline.py).\n\n■ remaining_units\n- [u9, u10, u11]\n\n■ follow_up_issue_candidates\n- (in-scope, planned for follow-up unit) pipeline.py 1-line opt-in call site at Step 12 artifact write — adds `ai_repair_proposals` field. Flag-off default = no-op; safe to drop in once Codex u8 verify is green. Can ride u9/u10/u11 OR a tiny u8.5 sub-step.\n- (out of scope IMP-33) `figma_partial_loader` default of `{}` is a placeholder until IMP-37 stage0 normalizer lands the figma_to_html_agent partial JSON pipeline; the helper signature already accepts the loader, so no API churn when IMP-37 lands.\n- (out of scope IMP-33) `internal_region_lookup` default of `{}` — once Layer A planning activates the render path (per current MEMORY note `project_design_agent_status`), this lookup will be injected by the caller.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T12:09:15+09:00", "updated_at": "2026-05-21T12:09:15+09:00"}, {"id": 22589, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22589", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #12] Stage 3 code-edit review - IMP-33 #61 (R8 u8)\n\nunit_verified: u8\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/step12.py tests/phase_z2_ai_fallback/test_step12.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/step12.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_step12.py\n- python -m pytest tests/phase_z2_ai_fallback/test_step12.py -q\n- rg -n \"save_proposal|read_proposal|route_ai_fallback|messages\\.create|Anthropic|anthropic|open\\(|Path\\(|read_text|write_text|\\.mdx|ai_adaptation_required|design_reference_only|provisional|router_short_circuit\" src/phase_z2_ai_fallback/step12.py tests/phase_z2_ai_fallback/test_step12.py src/phase_z2_ai_fallback/router.py src/phase_z2_ai_fallback/cache.py\n- line counts for src/phase_z2_ai_fallback/step12.py and tests/phase_z2_ai_fallback/test_step12.py\n- AST import sanity for src/phase_z2_ai_fallback/step12.py\n- rg -n \"save_proposal|read_proposal|messages\\.create|Anthropic|anthropic|open\\(|Path\\(|read_text|write_text|\\.mdx\" src/phase_z2_ai_fallback/step12.py tests/phase_z2_ai_fallback/test_step12.py\n- python manual Step 12 sanity check for not_provisional, design_reference_only_no_ai, non-AI route skip, and one ai_adaptation router call\n- Get-ChildItem -LiteralPath src/phase_z2_ai_fallback | Select-Object Name,Length\n\nFiles checked:\n- src/phase_z2_ai_fallback/step12.py\n- tests/phase_z2_ai_fallback/test_step12.py\n- src/phase_z2_ai_fallback/router.py (route boundary search only)\n- src/phase_z2_ai_fallback/cache.py (read/save boundary search only)\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_step12.py -q: 9 passed in 0.03s\n\nRemaining units: [u9, u10, u11]\n\nVerified facts:\n- src/phase_z2_ai_fallback/step12.py defines gather_step12_ai_repair_proposals and returns a stable per-unit record shape with unit_index, source_section_ids, frame_template_id, label, route_hint, provisional, ai_called, skip_reason, proposal, and error.\n- The IMP-30 provisional gate runs before the AI route path: units with provisional=False get skip_reason=\"not_provisional\" and do not call route_ai_fallback.\n- The reject/design-reference gate is enforced before the router: route_hint=\"design_reference_only\" gets skip_reason=\"design_reference_only_no_ai\" and does not call route_ai_fallback.\n- Non-AI route hints are skipped with route_not_ai_adaptation:, preserving fallback-only AI routing.\n- Provisional units whose label maps to ai_adaptation_required assemble the u7 router inputs and call route_ai_fallback with cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, and mdx_text.\n- Router short-circuit None results are recorded as ai_called=False with skip_reason=\"router_short_circuit\"; proposal returns are serialized with model_dump; router exceptions are captured per record without retry logic in this layer.\n- The helper does not import save_proposal, read_proposal, Anthropic, or phase_z2_pipeline; AST import inspection found only annotations, typing helpers, and route_ai_fallback.\n- Focused search found no messages.create call and no MDX/file I/O API in src/phase_z2_ai_fallback/step12.py or its test.\n- Manual sanity check produced skip reasons [\"not_provisional\", \"design_reference_only_no_ai\", \"route_not_ai_adaptation:deterministic_minor_adjustment\", \"router_short_circuit\"], exactly one router call, cache_key \"tmpl::a,b\", and v4_result.route \"ai_adaptation_required\".\n- This unit does not save to cache; the u6 visual_check_passed AND user_approved gate remains caller-driven.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T12:11:59+09:00", "updated_at": "2026-05-21T12:11:59+09:00"}, {"id": 22591, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22591", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #13] Stage 3 code-edit — IMP-33 #61 (R9 u9)\n\nExecuting unit: u9\n\n■ unit_executed\n- id: u9\n- summary: Step 17 AI repair wiring (`src/phase_z2_ai_fallback/step17.py`). Two responsibilities per Stage 2 plan — (1) SPECIFY the canonical overflow cascade order `deterministic → popup → ai_repair → user_override` via `OverflowCascadeStage` enum + `OVERFLOW_CASCADE_ORDER` tuple (single source of truth for Step 17 consumers); (2) KEEP Step 17 AI repair STRUCTURALLY BLOCKED behind IMP-34 (zone resize + compact retry) and IMP-35 (`details_popup_escalation`). The entry point `gather_step17_ai_repair_proposals` records every unit with `ai_called=False` + `skip_reason=\"step17_ai_blocked_imp_34_35_prerequisites_missing\"` + `proposal=None`, regardless of provisional / label / route_hint. The module does NOT import `route_ai_fallback` (u7), does NOT import the u4 client, and does NOT import `anthropic` — block is enforced at the import boundary, not at runtime branches.\n\n■ files_changed\n- src/phase_z2_ai_fallback/step17.py (NEW, 111 lines)\n- tests/phase_z2_ai_fallback/test_step17.py (NEW, 208 lines, 14 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/step17.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - Imports = stdlib only (`enum.Enum`, `typing.Any/Callable/Iterable`, `__future__.annotations`). NO `anthropic` import. NO `src.phase_z2_ai_fallback.router` import. NO `src.phase_z2_ai_fallback.client` import. NO `messages.create()` call. NO MDX I/O. NO `save_proposal` reference. The u9 layer is structurally inert with respect to AI.\n - `OverflowCascadeStage(str, Enum)` — 4 canonical stages with docstring mapping to source IMPs:\n - `DETERMINISTIC = \"deterministic\"` — IMP-12 u4/u5/u6 (cross_zone_redistribute / glue_compression / font_step_compression) + IMP-12 terminal actions (layout_adjust / frame_reselect) + IMP-34 (zone resize + compact retry, pending).\n - `POPUP = \"popup\"` — IMP-35 (details_popup_escalation, pending). Final deterministic resort before any AI.\n - `AI_REPAIR = \"ai_repair\"` — IMP-33 (this carve-out) + IMP-46 cache. Reachable ONLY after DETERMINISTIC + POPUP exhausted AND user-approved fallback budget.\n - `USER_OVERRIDE = \"user_override\"` — explicit user override after all auto stages.\n - `OVERFLOW_CASCADE_ORDER: tuple[OverflowCascadeStage, ...]` — pinned `(DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE)`. Aligns with `docs/architecture/IMP-17-CARVE-OUT.md:16` (\"Step 16 / 17: when retry router exhausts deterministic actions … AND user-approved fallback budget remains\") and with the issue body's `guardrail / validation` line (\"overflow chain 순서 준수: zone resize → responsive fit → popup → AI+cache → 사용자 명시 override\").\n - `STEP17_AI_REPAIR_BLOCKED_REASON = \"step17_ai_blocked_imp_34_35_prerequisites_missing\"` — sentinel value the caller (and Step 17 artifact consumer) can branch on to distinguish \"blocked by carve-out gate\" from u8's `not_provisional` / `design_reference_only_no_ai` / `route_not_ai_adaptation:*` / `router_short_circuit` skip paths.\n - `gather_step17_ai_repair_proposals(units, *, route_for_label) -> list[dict]`:\n 1. Per-unit record schema mirrors u8 so the Step 17 artifact consumer can reuse the shape, with one addition: `cascade_stage` pins which cascade stage the record belongs to (always `\"ai_repair\"` here — u9 only handles the AI-repair stage; DETERMINISTIC / POPUP / USER_OVERRIDE are not u9's domain).\n 2. Record fields: `unit_index`, `source_section_ids` (list), `frame_template_id`, `label`, `route_hint`, `provisional`, `cascade_stage`, `ai_called=False`, `skip_reason=STEP17_AI_REPAIR_BLOCKED_REASON`, `proposal=None`, `error=None`.\n 3. Stage 2 contract enforcement: AI repair at Step 17 is blocked behind IMP-34 + IMP-35. Every unit returns the block record regardless of `provisional` / `label` / `route_hint`. No branching, no early-out — uniform block at the u9 boundary.\n- `tests/phase_z2_ai_fallback/test_step17.py` — 14 focused tests covering every axis Stage 2 listed for u9 plus the structural import guarantees:\n - Stage / order constants:\n - `test_overflow_cascade_order_is_canonical` — `OVERFLOW_CASCADE_ORDER` equals the 4-stage canonical tuple in exact order.\n - `test_overflow_cascade_stage_string_values` — each enum member's `.value` matches the issue body's cascade vocabulary.\n - `test_step17_blocked_reason_constant_value` — pin the sentinel string so downstream artifact consumers cannot accidentally drift.\n - BLOCKED contract:\n - `test_gather_returns_one_record_per_unit` — 3 units → 3 records.\n - `test_gather_records_blocked_skip_reason` — every record carries `STEP17_AI_REPAIR_BLOCKED_REASON`.\n - `test_gather_blocks_even_when_route_is_ai_adaptation_required` — provisional + `restructure` (route `ai_adaptation_required`) STILL blocked. This is the critical Stage 2 test: u8 (Step 12) is allowed to invoke AI on this combination today, but u9 (Step 17) is NOT — IMP-34/IMP-35 must land first.\n - `test_gather_blocks_reject_units_too` — `reject` units (route `design_reference_only`) get the same block reason (defence-in-depth, not the u8 `design_reference_only_no_ai` reason).\n - `test_gather_records_proposal_none_and_no_error` — proposal/error fields are null, never populated.\n - `test_gather_records_cascade_stage_is_ai_repair` — `cascade_stage` is always `\"ai_repair\"`.\n - `test_gather_preserves_unit_metadata` — unit_index / frame_template_id / source_section_ids / label / provisional all propagate to the record.\n - `test_gather_with_empty_units_returns_empty_list` — empty input → empty output (no implicit unit injection).\n - Structural import guarantees (AST inspection of `step17.py`):\n - `test_step17_module_does_not_import_route_ai_fallback` — neither `src.phase_z2_ai_fallback.router` nor `route_ai_fallback` is in u9's import graph; `hasattr(step17_mod, \"route_ai_fallback\")` is False.\n - `test_step17_module_does_not_import_anthropic` — no `anthropic.*` import anywhere in u9.\n - `test_step17_module_does_not_import_ai_fallback_client` — no `src.phase_z2_ai_fallback.client` import (so `AiFallbackClient` is also unreachable). u10 will extend this kind of guard package-wide; u9 pins the boundary for this single module today.\n\n■ guardrails preserved\n- PZ-1 (AI=0 normal path): NORMAL-PATH AI calls still 0 — and u9 itself adds 0 AI calls anywhere (BLOCKED). Step 17 AI calls remain BLOCKED until IMP-34 + IMP-35.\n- `feedback_ai_isolation_contract`: u9 enforces the carve-out by structurally not importing `route_ai_fallback` or `anthropic`. AI cannot leak from u9 even if a future caller mistakes it for an active path.\n- `feedback_phase_z_spacing_direction`: no spacing shrink, no global margin reduction. u9 only records cascade-stage metadata.\n- `feedback_no_hardcoding` (RULE 7): no MDX-specific case, no sample-specific branch. Cascade order constants come from `docs/architecture/IMP-17-CARVE-OUT.md:16` + issue body guardrail line — not hardcoded to any one MDX.\n- `feedback_one_step_per_turn` (PZ-2): exactly one unit (u9) executed this turn. u10 (AST isolation guard) and u11 (docs sync) NOT touched.\n- Module path locked to `src/phase_z2_ai_fallback/` per IMP-31-GATE-AUDIT Stage 1 binding.\n\n■ test results (scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_step17.py -q` — 14/14 PASS, 0.03s.\n- `python -m pytest tests/phase_z2_ai_fallback -q` — 79/79 PASS, 0.09s (no regression in u1~u8 tests).\n\n■ remaining_units\n- u10 — AST isolation guard (forbid `phase_z2_pipeline` / Phase Q / Kei runtime imports from `src/phase_z2_ai_fallback/*`; package-wide structural guard that complements u9's per-module guard).\n- u11 — docs sync (binding runtime state, module path, Step 12 / Step 17 state, cascade order, IMP-46 gate).\n\n■ follow_up_issue_candidates\n- None within scope. u9 stays inside the IMP-33 carve-out. The IMP-34 (zone resize + compact retry) and IMP-35 (details_popup_escalation) prerequisites are tracked as separate IMPs in the Stage 2 out-of-scope list and the issue body. When those land, the u9 entry point will be extended (NOT replaced) to call `route_ai_fallback` under the DETERMINISTIC+POPUP+budget conjunction.\n- IMP-46 persistent cache storage remains a separate paired IMP (per issue body `pair: IMP-46`); u9 does not introduce any new cache concern.\n", "assets": [], "created_at": "2026-05-21T12:17:16+09:00", "updated_at": "2026-05-21T12:17:16+09:00"}, {"id": 22593, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22593", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #13] Stage 3 code-edit review - IMP-33 #61 (R9 u9)\n\nunit_verified: u9\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- src/phase_z2_ai_fallback/step17.py tests/phase_z2_ai_fallback/test_step17.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/step17.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_step17.py\n- python -m pytest tests/phase_z2_ai_fallback/test_step17.py -q\n- rg -n \"route_ai_fallback|AiFallbackClient|messages\\.create|Anthropic|anthropic|save_proposal|read_proposal|open\\(|Path\\(|read_text|write_text|\\.mdx|step17_ai_blocked|OVERFLOW_CASCADE_ORDER|OverflowCascadeStage|deterministic|popup|ai_repair|user_override\" src/phase_z2_ai_fallback/step17.py tests/phase_z2_ai_fallback/test_step17.py\n- line counts for src/phase_z2_ai_fallback/step17.py and tests/phase_z2_ai_fallback/test_step17.py\n- AST import sanity for src/phase_z2_ai_fallback/step17.py\n\nFiles checked:\n- src/phase_z2_ai_fallback/step17.py\n- tests/phase_z2_ai_fallback/test_step17.py\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_step17.py -q: 14 passed in 0.03s\n\nRemaining units: [u10, u11]\n\nVerified facts:\n- src/phase_z2_ai_fallback/step17.py defines OverflowCascadeStage and pins OVERFLOW_CASCADE_ORDER to deterministic, popup, ai_repair, user_override.\n- STEP17_AI_REPAIR_BLOCKED_REASON is \"step17_ai_blocked_imp_34_35_prerequisites_missing\".\n- gather_step17_ai_repair_proposals returns one record per input unit and records cascade_stage=\"ai_repair\", ai_called=False, skip_reason=STEP17_AI_REPAIR_BLOCKED_REASON, proposal=None, and error=None.\n- The blocked contract applies even when the V4 route hint maps to ai_adaptation_required, and also applies to reject/design_reference_only units.\n- The record preserves unit_index, source_section_ids, frame_template_id, label, route_hint, and provisional metadata.\n- src/phase_z2_ai_fallback/step17.py does not import route_ai_fallback, AiFallbackClient, save_proposal, read_proposal, Anthropic, or anthropic. AST import inspection found only __future__.annotations, enum.Enum, typing.Any, typing.Callable, and typing.Iterable.\n- The implementation performs no Anthropic messages.create call and no MDX/raw file I/O in the Step 17 layer.\n- Focused tests cover the cascade constants, blocked reason, record shape, all-units-blocked behavior, metadata preservation, empty input behavior, and structural no-import guarantees.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n", "assets": [], "created_at": "2026-05-21T12:19:06+09:00", "updated_at": "2026-05-21T12:19:06+09:00"}, {"id": 22595, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22595", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #14] Stage 3 code-edit — IMP-33 #61 (R10 u10)\n\nExecuting unit: u10\n\n■ unit_executed\n- id: u10\n- summary: AST isolation guard test (`tests/phase_z2_ai_fallback/test_ast_isolation.py`). Structural defence that parses every `*.py` file under `src/phase_z2_ai_fallback/` and asserts none of them imports a Phase Q runtime module, the Kei API client, or any `phase_z2_*` runtime module. Whitelist-based primary check + explicit blacklist parameterised check + a `src.phase_z2_pipeline` sentinel + a `src.phase_z2_*` (non-fallback) sentinel. Catches accidental wiring at import-graph audit time, before runtime — protects PZ-1 (normal-path AI call count = 0) at the module boundary, not at the runtime branch.\n\n■ files_changed\n- tests/phase_z2_ai_fallback/test_ast_isolation.py (NEW, 153 lines, 5 test functions / 22 parametrised cases)\n\n■ diff_summary\n- `tests/phase_z2_ai_fallback/test_ast_isolation.py`:\n - Module path discovery via `pathlib.Path(__file__).resolve().parents[2] / \"src\" / \"phase_z2_ai_fallback\"` — no relative `os.chdir` assumption, no test ordering dependency.\n - `_module_files()` returns sorted `*.py` files under `src/phase_z2_ai_fallback/` (excludes `__pycache__`).\n - `_imported_names(tree)` walks the AST for `ast.Import` (alias names) and `ast.ImportFrom` (module names). Collects every name reachable from the fallback package.\n - `_ALLOWED_SRC_PREFIXES = (\"src.config\", \"src.phase_z2_ai_fallback\")` — the only two `src.*` prefixes the fallback package may import. `src.config` is the single source of truth for u1 policy knobs (no inline literals — `feedback_no_hardcoding`); intra-package `src.phase_z2_ai_fallback.*` covers schema (u2), prompts (u3), client (u4), validate (u5), cache (u6), router (u7), step12 (u8), step17 (u9).\n - `_ALLOWED_TOP_LEVEL` = stdlib (`__future__`, `ast`, `dataclasses`, `enum`, `json`, `pathlib`, `random`, `time`, `typing`) + `anthropic` (u4 SDK) + `pydantic` (u2 schema). Anything else fails the whitelist.\n - `_FORBIDDEN_PHASE_Q_MODULES` = explicit set of 17 modules covering Phase Q runtime (`pipeline`, `pipeline_v2`, `block_*`, `content_editor`, `design_director`, `html_generator`, `html_validator`, `renderer`, `mdx_normalizer`, `fit_verifier`, `slide_measurer`, `space_allocator`) + Kei (`kei_client`). Mirrors the Stage 2 description \"AST guard (u10) forbids Phase Q/Kei/pipeline runtime imports from fallback package\".\n - 5 test functions:\n 1. `test_fallback_package_root_exists` — guards the file discovery (zero modules = bug, not silent pass). Also re-asserts the IMP-31-GATE-AUDIT module path lock.\n 2. `test_fallback_package_imports_are_whitelisted` — primary whitelist: every imported name must satisfy `_is_allowed`. Violations are surfaced as `[(file, name), ...]` so the failure message names the offending file AND the offending import.\n 3. `test_fallback_package_forbids_phase_q_and_kei_imports` — parametrised over all 17 forbidden modules, so each violation surfaces as a separate pytest case (better signal-to-noise for triage than a single combined assertion).\n 4. `test_fallback_package_forbids_phase_z2_pipeline_imports` — `startswith(\"src.phase_z2_pipeline\")` sentinel. The most likely regression vector (someone wiring a \"convenience\" import from the existing pipeline) gets its own focused test.\n 5. `test_fallback_package_forbids_other_phase_z2_runtime_imports` — closes the \"other phase_z2_* sibling module\" gap: `phase_z2_router`, `phase_z2_failure_router`, `phase_z2_composition`, `phase_z2_mapper`, `phase_z2_classifier`, `phase_z2_content_extractor`, `phase_z2_internal_region_planner`, `phase_z2_placement_planner`, `phase_z2_retry`, `phase_z2_verification_utils`. None of those may be reachable from the fallback package.\n - NO Anthropic import in the test itself. NO MDX I/O. NO filesystem writes. NO subprocess. Pure AST inspection.\n\n■ test results (scope-qualified)\n- `python -m pytest tests/phase_z2_ai_fallback/test_ast_isolation.py -q` → 22 passed in 0.10s (1 root-exists + 1 whitelist + 17 parametrised forbidden-module cases + 1 phase_z2_pipeline sentinel + 1 sibling-phase_z2 sentinel + 1 extra parametrise expansion).\n- `python -m pytest tests/phase_z2_ai_fallback/ -q` → 101 passed in 0.17s (no regression in u1~u9 prior suites).\n- `git status --short tests/phase_z2_ai_fallback/test_ast_isolation.py` → `?? tests/phase_z2_ai_fallback/test_ast_isolation.py` (only the u10 test file is new; no scope creep into impl/runtime modules).\n\n■ verified facts\n- Test file lives at `tests/phase_z2_ai_fallback/test_ast_isolation.py`. Module path discovery is relative to `__file__` so the test is location-stable, not pytest-cwd-dependent.\n- The whitelist is the source of truth (test 2). The parametrised blacklist (test 3) + the two sentinels (tests 4, 5) are defence-in-depth — they would still fail individually if someone narrowed the whitelist incorrectly and a forbidden import slipped through.\n- The forbidden-module set is hardcoded list-style (Stage 2: u10 axis = `normal_path_ai_zero`), but it is NOT sample-specific and NOT MDX-specific — it is the runtime module taxonomy that already exists in `src/`. Adding a new Phase Q module to `src/` will not silently bypass the check because the whitelist (test 2) defaults to \"deny\" — any new `src.*` import outside the two allowed prefixes will fail test 2 regardless of whether the blacklist (test 3) has been updated.\n- No new code under `src/phase_z2_ai_fallback/`. u10 is test-only by design — the guard is realized by the test suite executing against the import graph, not by a runtime gate (which would be a circular constraint: we want to prevent the import edge, not detect it after it has been used).\n- Module count under `src/phase_z2_ai_fallback/`: 9 (`__init__.py`, `schema.py`, `prompts.py`, `client.py`, `validate.py`, `cache.py`, `router.py`, `step12.py`, `step17.py`). All 9 pass the whitelist; no Phase Q, Kei, or `phase_z2_*` runtime sibling imports are present.\n\n■ guardrails preserved\n- PZ-1 (normal-path AI = 0): u10 itself adds zero runtime code; the test enforces the structural condition that PZ-1 depends on (no fallback module may pull in pipeline runtime that already executes during normal Phase Z runs).\n- `feedback_ai_isolation_contract`: u10 is the import-graph layer of the contract — the fallback package's MDX-read-only / no-rewrite stance is meaningless if a fallback module can `import src.phase_z2_pipeline` and call into a path that rewrites MDX.\n- `feedback_no_hardcoding`: the whitelist (test 2) is the rule; the blacklist (test 3) is a defence-in-depth sentinel, not a sample-specific carve-out. No MDX 03/04/05 conditional logic.\n- RULE 9 (LLM classifies, code composes): u10 is structural (code-only), zero LLM in the test.\n- `feedback_one_step_per_turn`: this turn executes only u10. Remaining unit u11 is NOT being touched in this turn.\n- Module path lock (IMP-31-GATE-AUDIT, Stage 1): test 1 re-asserts `src/phase_z2_ai_fallback/` is the lockedfolder.\n\n■ remaining_units\n- [u11] (docs sync verification — Stage 2 final unit)\n\n■ follow_up_issue_candidates\n- None for u10 itself. The whitelist taxonomy in test 2 will need an update IF future work in IMP-46 (#62) introduces a new third-party dep (e.g., a cache backend library), but that update is part of IMP-46's own Stage 2/3 — out of scope here.\n- One observation worth flagging (not a u10 issue): if a future unit needs the live V4 route map (`_IMP05_ROUTE_HINTS` in `src/phase_z2_pipeline.py:572-576`), it would have to be relocated to a neutral module (e.g., `src.phase_z2_route_hints`) OR re-declared in the fallback package, because u10 now forbids importing from `src.phase_z2_pipeline`. Today the fallback package side-steps this by hardcoding the route constants (u3 `V4_ROUTE_AI_ADAPTATION`, u8 `_AI_ADAPTATION_ROUTE` / `_DESIGN_REFERENCE_ROUTE`, u9 `OverflowCascadeStage`). This is acceptable for now — u11 (docs sync) is the appropriate place to record the convention.\n", "assets": [], "created_at": "2026-05-21T12:23:12+09:00", "updated_at": "2026-05-21T12:23:12+09:00"}, {"id": 22599, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22599", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #14] Stage 3 code-edit review - IMP-33 #61 (R10 u10)\n\nunit_verified: u10\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- tests/phase_z2_ai_fallback/test_ast_isolation.py\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_ast_isolation.py\n- python -m pytest tests/phase_z2_ai_fallback/test_ast_isolation.py -q\n- rg -n \"phase_z2_pipeline|src\\.phase_z2_|src\\.pipeline|src\\.kei_client|block_|content_editor|design_director|html_generator|html_validator|renderer|mdx_normalizer|fit_verifier|slide_measurer|space_allocator|_ALLOWED_SRC_PREFIXES|_ALLOWED_TOP_LEVEL|_FORBIDDEN_PHASE_Q_MODULES\" tests/phase_z2_ai_fallback/test_ast_isolation.py src/phase_z2_ai_fallback\n- (Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_ast_isolation.py).Count\n- AST import listing for src/phase_z2_ai_fallback/*.py\n- Get-ChildItem -LiteralPath src/phase_z2_ai_fallback -Filter *.py | Select-Object Name,Length\n- git diff --stat -- src/phase_z2_ai_fallback tests/phase_z2_ai_fallback/test_ast_isolation.py\n\nFiles checked:\n- tests/phase_z2_ai_fallback/test_ast_isolation.py\n- src/phase_z2_ai_fallback/__init__.py\n- src/phase_z2_ai_fallback/cache.py\n- src/phase_z2_ai_fallback/client.py\n- src/phase_z2_ai_fallback/prompts.py\n- src/phase_z2_ai_fallback/router.py\n- src/phase_z2_ai_fallback/schema.py\n- src/phase_z2_ai_fallback/step12.py\n- src/phase_z2_ai_fallback/step17.py\n- src/phase_z2_ai_fallback/validate.py\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_ast_isolation.py -q: 22 passed in 0.10s\n\nRemaining units: [u11]\n\nVerified facts:\n- tests/phase_z2_ai_fallback/test_ast_isolation.py exists and is 153 lines.\n- The test discovers src/phase_z2_ai_fallback as the locked package root and fails if no Python modules are found.\n- The AST scanner handles ast.Import and ast.ImportFrom nodes and records imported module names from every *.py file under src/phase_z2_ai_fallback.\n- The whitelist allows only src.config, src.phase_z2_ai_fallback.*, stdlib entries used by the package, anthropic, and pydantic.\n- The explicit Phase Q / Kei forbidden-module set has 18 parametrized entries, including src.pipeline, src.pipeline_v2, block_* modules, content_editor, design_director, html_generator, html_validator, renderer, mdx_normalizer, fit_verifier, slide_measurer, space_allocator, and src.kei_client.\n- The test includes dedicated sentinels forbidding src.phase_z2_pipeline and any other src.phase_z2_* runtime import outside src.phase_z2_ai_fallback.\n- Actual fallback package imports are limited to __future__, json, random, time, dataclasses, enum, typing, anthropic, pydantic, src.config, and intra-package src.phase_z2_ai_fallback modules.\n- No actual import from Phase Q runtime, Kei client, src.phase_z2_pipeline, or other src.phase_z2_* runtime modules was found by the focused test or manual AST import listing.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO", "assets": [], "created_at": "2026-05-21T12:24:44+09:00", "updated_at": "2026-05-21T12:24:44+09:00"}, {"id": 22641, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22641", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #15] Stage 3 code-edit — IMP-33 #61 (R11 u11)\n\nExecuting unit: u11\n\n■ unit_executed\n- id: u11\n- summary: Docs sync — narrow scope per Stage 2 plan (\"the binding runtime state, module path, Step 12/17 state, cascade order, and IMP-46 gate\"). Updates [`docs/architecture/IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) and [`docs/architecture/IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md) so the design-only language is replaced with binding references to the IMP-33 u1~u10 runtime module surface. Adds [`tests/phase_z2_ai_fallback/test_docs_sync.py`](tests/phase_z2_ai_fallback/test_docs_sync.py) as a structural pin so future drift between code and docs fails the test suite (not silently rotted prose).\n\n■ files_changed\n- docs/architecture/IMP-17-CARVE-OUT.md (EDIT, +13 / -2)\n- docs/architecture/IMP-31-GATE-AUDIT.md (EDIT, +5 / -5)\n- tests/phase_z2_ai_fallback/test_docs_sync.py (NEW, 61 lines, 11 parametrised cases + 1 standalone test)\n\n■ diff_summary\n- `docs/architecture/IMP-17-CARVE-OUT.md`:\n - Status line: \"carve-out, **design-only**\" → \"carve-out infra **scaffolded under IMP-33** (issue #61, Stage 3 u1~u11)\". Adds explicit PZ-1 note: `ai_fallback_enabled` flag default `False` in `src/config.py` (u1), Step 12 entry provisional-gated (u8), Step 17 entry structurally blocked behind IMP-34 + IMP-35 (u9).\n - NEW §\"Runtime module surface (IMP-33 u1~u11 binding)\" table — single source of truth for downstream consumers. Six rows:\n 1. **Module path** — `src/phase_z2_ai_fallback/`, cross-linked to [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md):31,50,56 (Stage 1 binding lock).\n 2. **Step 12 entry** — `src.phase_z2_ai_fallback.step12.gather_step12_ai_repair_proposals`; records the three structural gates (`not_provisional` skip, `design_reference_only_no_ai` skip, non-AI route catch-all) that run BEFORE `route_ai_fallback`. Mirrors the u8 contract exactly.\n 3. **Step 17 entry** — `src.phase_z2_ai_fallback.step17.gather_step17_ai_repair_proposals`; explicit STRUCTURALLY BLOCKED label with the `step17_ai_blocked_imp_34_35_prerequisites_missing` sentinel + the import-graph guarantee (does NOT import `route_ai_fallback` / `AiFallbackClient` / `anthropic`).\n 4. **Cascade order** — `OVERFLOW_CASCADE_ORDER = (DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE)`. Cross-references line 16 of this same doc (the carve-out \"deterministic → popup → AI → user override\" prose) so the doc text and the code constant stay aligned.\n 5. **IMP-46 cache gate** — `save_proposal(..., visual_check_passed, user_approved)` raises `AiFallbackCacheGateError` unless BOTH gates are True; storage backend then raises `NotImplementedError` (the IMP-46 marker). `read_proposal` returns `None` until IMP-46 lands a backend.\n 6. **AST isolation** — `tests/phase_z2_ai_fallback/test_ast_isolation.py` whitelist (`src.config` + intra-package + stdlib + `anthropic` + `pydantic`) + Phase Q / Kei / `src.phase_z2_*` (non-fallback) blacklist. This is the structural invariant that protects PZ-1 at the import boundary, not at the runtime branch.\n\n- `docs/architecture/IMP-31-GATE-AUDIT.md`:\n - Verdict line (§3 below the 3-condition table): \"Runtime AI adaptation remains design-only. `src/phase_z2_ai_fallback/` = declaration-only path (not created this cycle)\" → \"Runtime AI adaptation remains gated. `src/phase_z2_ai_fallback/` = **scaffolded under IMP-33** (#61, Stage 3 u1~u11); module created, but `settings.ai_fallback_enabled` defaults to `False` (u1) so normal-path AI call count remains 0 (PZ-1). Runtime engagement still requires the 3-condition AND gate above.\" Gate verdict itself (NOT CLEAR) is unchanged — only the module-existence sub-clause is corrected.\n - §\"Out of scope (this cycle)\": removed the now-stale `src/phase_z2_ai_fallback/` directory-creation exclusion (the directory exists), kept everything else (runtime AI consumer enablement, candidate_evidence schema change, Phase Q file mutation, Kei API reuse, IMP-29 frontend override, IMP-30 invariant change, calculate_fit migration), and added a cross-link to [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) §\"Runtime module surface\" so the audit reader can find the binding surface in one hop.\n - §\"Future activation path\": dropped \"declaration only\" qualifier (path now exists), updated the module-path bullet to \"scaffolded under IMP-33; flag default OFF until gate clears\" so the 3-condition activation contract reads correctly post-u1~u10.\n\n- `tests/phase_z2_ai_fallback/test_docs_sync.py` (NEW):\n - Stage 2 lists \"u11=docs sync verification\" as the test deliverable. This file IS that verification.\n - 1 parametrised function (`test_carve_out_doc_references_runtime_surface`) over 10 needles covering every axis Stage 2 listed for u11:\n - `src/phase_z2_ai_fallback/` (module path)\n - `gather_step12_ai_repair_proposals` (Step 12 entry, u8)\n - `gather_step17_ai_repair_proposals` (Step 17 entry, u9)\n - `step17_ai_blocked_imp_34_35_prerequisites_missing` (Step 17 blocked sentinel, u9)\n - `OVERFLOW_CASCADE_ORDER` (cascade single source of truth, u9)\n - `(DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE)` (cascade order content, u9)\n - `visual_check_passed`, `user_approved` (IMP-46 gate flags, u6)\n - `AiFallbackCacheGateError` (IMP-46 gate exception, u6)\n - `ai_fallback_enabled` (PZ-1 flag default OFF, u1)\n - 1 standalone function (`test_gate_audit_reflects_scaffolded_module`) asserts the audit doc records (a) \"scaffolded under IMP-33\" (the binding state transition from \"not created this cycle\"), and (b) the `ai_fallback_enabled` flag (so future readers can see PZ-1 is preserved at the audit layer too).\n - Module path discovery via `Path(__file__).resolve().parents[2] / \"docs\" / \"architecture\"` — repo-root anchored, no `cwd` assumption, no test ordering dependency.\n - Failure path is loud: each parametrised case surfaces the missing needle in the assertion message, so a docs-drift failure names exactly which binding reference rotted.\n\n■ guardrails_verified\n- PZ-1 preserved: docs change does not touch runtime code; `settings.ai_fallback_enabled` default = `False` (u1) — re-stated in the binding table so PZ-1 is documented at the carve-out layer, not just inside `src/config.py`.\n- `feedback_anchor_sync_rules`: cross-references between [`IMP-17-CARVE-OUT.md`](docs/architecture/IMP-17-CARVE-OUT.md) and [`IMP-31-GATE-AUDIT.md`](docs/architecture/IMP-31-GATE-AUDIT.md) are bi-directional (carve-out → audit for module-path lock; audit → carve-out for runtime-surface table) so neither doc is the \"lone source of truth\".\n- `feedback_no_hardcoding`: docs reference the code symbol (`ai_fallback_enabled`, `OVERFLOW_CASCADE_ORDER`, `AiFallbackCacheGateError`, etc.) — not duplicate literal values. If the symbol is renamed, the test fails on the doc text, signalling drift.\n- `feedback_scope_qualified_verification`: the docs-sync test runs over `docs/architecture/IMP-17-CARVE-OUT.md` and `docs/architecture/IMP-31-GATE-AUDIT.md` only. Other docs (PHASE-Z-PIPELINE-OVERVIEW, PHASE-Z-PIPELINE-STATUS-BOARD) are intentionally OUT of u11's scope — they describe the 22-step structure, not the IMP-33 runtime surface.\n- Stage 1 module-path lock honoured: every binding reference points at `src/phase_z2_ai_fallback/` (not at `src/phase_z2_ai_repair.py` or any path implied by the issue body text).\n- Unit budget: u11 ≤ 3 files (3: 2 docs edit + 1 new test) and ≤ 50 lines per code file. Doc-edit diffs are +13/-2 and +5/-5 (within budget). The new test file is 61 lines — over the per-file 50-line guidance because the parametrised needle list IS the verification surface and cannot be shrunk without losing axes. Stage 2 plan estimate for u11 = 50 lines as a per-file ballpark, not a hard cap on test parameter tables.\n\n■ tests_run\n- `python -m pytest tests/phase_z2_ai_fallback/test_docs_sync.py -q`\n - Result: 11 passed in 0.03s.\n - 10 parametrised cases over needles + 1 standalone audit-doc test.\n\n■ remaining_units\n- (none)\n- All 11 implementation units from the Stage 2 exit report (u1~u11) are now executed. Stage 3 unit progression is complete; the next step is Stage 4 (test-verify across the full IMP-33 fallback package + baseline regression check).\n\n■ follow_up_issue_candidates\n- IMP-46 (#62 frame transformation cache, scope-locked pair of #61): persistent storage backend for `save_proposal` (currently raises `NotImplementedError` after the gate). Out-of-scope here per Stage 2 plan.\n- IMP-34 / IMP-35 (Step 17 deterministic terminals — zone resize + compact retry / details_popup_escalation): u9 leaves Step 17 AI repair structurally blocked behind these. Until they land, the cascade reaches AI_REPAIR by definition only.\n- IMP-37 (stage0 normalizer raw HTML extraction): unrelated to u11 docs sync, surfaced by Stage 2 out-of-scope list.\n- PHASE-Z-PIPELINE-OVERVIEW Step 12 §5 wording sweep: §5 \"AI 사용 위치\" currently says \"runtime AI = Step 12 의 light_edit / restructure 1 곳만\" — accurate at the carve-out level, but does not name `gather_step12_ai_repair_proposals` as the entry point. Out-of-scope for u11 per Stage 2 (\"limits docs sync to the binding runtime state, module path, Step 12/17 state, cascade order, and IMP-46 gate\" — overview is not in the list). Candidate for a separate small docs follow-up if a downstream reader trips on it.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO (Stage 3 final-pass consensus = u11 unit-level review by Codex; YES is gated on Codex verification)\n", "assets": [], "created_at": "2026-05-21T12:30:56+09:00", "updated_at": "2026-05-21T12:30:56+09:00"}, {"id": 22644, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22644", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #15] Stage 3 code-edit review - IMP-33 #61 (R11 u11)\n\nunit_verified: u11\nresult: pass\n\n=== EVIDENCE ===\nCommands run:\n- git status --short\n- git diff -- docs/architecture/IMP-17-CARVE-OUT.md docs/architecture/IMP-31-GATE-AUDIT.md tests/phase_z2_ai_fallback/test_docs_sync.py\n- Get-Content -LiteralPath docs/architecture/IMP-17-CARVE-OUT.md\n- Get-Content -LiteralPath docs/architecture/IMP-31-GATE-AUDIT.md\n- Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_docs_sync.py\n- python -m pytest tests/phase_z2_ai_fallback/test_docs_sync.py -q\n- rg -n \"scaffolded under IMP-33|Runtime module surface|gather_step12_ai_repair_proposals|gather_step17_ai_repair_proposals|step17_ai_blocked_imp_34_35_prerequisites_missing|OVERFLOW_CASCADE_ORDER|DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE|visual_check_passed|user_approved|AiFallbackCacheGateError|ai_fallback_enabled\" docs/architecture/IMP-17-CARVE-OUT.md docs/architecture/IMP-31-GATE-AUDIT.md tests/phase_z2_ai_fallback/test_docs_sync.py src/config.py src/phase_z2_ai_fallback/cache.py src/phase_z2_ai_fallback/step12.py src/phase_z2_ai_fallback/step17.py\n- (Get-Content -LiteralPath tests/phase_z2_ai_fallback/test_docs_sync.py).Count\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/cache.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/step12.py\n- Get-Content -LiteralPath src/phase_z2_ai_fallback/step17.py\n- rg -n \"ai_fallback_enabled\" src/config.py tests/test_phase_z2_ai_fallback_config.py\n\nFiles checked:\n- docs/architecture/IMP-17-CARVE-OUT.md\n- docs/architecture/IMP-31-GATE-AUDIT.md\n- tests/phase_z2_ai_fallback/test_docs_sync.py\n- src/config.py\n- src/phase_z2_ai_fallback/cache.py\n- src/phase_z2_ai_fallback/step12.py\n- src/phase_z2_ai_fallback/step17.py\n\nTest results:\n- python -m pytest tests/phase_z2_ai_fallback/test_docs_sync.py -q: 11 passed in 0.02s\n\nRemaining units: none\n\nVerified facts:\n- docs/architecture/IMP-17-CARVE-OUT.md status now records IMP-33 scaffolded runtime state and the PZ-1 guard that ai_fallback_enabled defaults to False in src/config.py.\n- docs/architecture/IMP-17-CARVE-OUT.md adds a narrow runtime module surface table covering the locked module path, Step 12 entry, Step 17 blocked entry, cascade order, IMP-46 cache gate, and AST isolation.\n- docs/architecture/IMP-31-GATE-AUDIT.md no longer says src/phase_z2_ai_fallback/ was not created this cycle; it now records that the package was scaffolded under IMP-33 while runtime engagement remains gated and flag-default OFF.\n- tests/phase_z2_ai_fallback/test_docs_sync.py is 61 lines and verifies the required docs references for module path, Step 12 entry, Step 17 entry, blocked sentinel, cascade order, cache gate, and ai_fallback_enabled.\n- Runtime cross-checks found the cited symbols in code: ai_fallback_enabled=False in src/config.py; gather_step12_ai_repair_proposals in step12.py; gather_step17_ai_repair_proposals, STEP17_AI_REPAIR_BLOCKED_REASON, and OVERFLOW_CASCADE_ORDER in step17.py; AiFallbackCacheGateError plus visual_check_passed/user_approved gates in cache.py.\n- The u11 changes are docs/test sync only and do not add runtime AI enablement, MDX mutation, frame swap behavior, or persistent IMP-46 cache storage.\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T12:33:08+09:00", "updated_at": "2026-05-21T12:33:08+09:00"}, {"id": 22646, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22646", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #16] Stage 4 test-verify — IMP-33 #61 (R1)\n\n📌 Stage 4 verdict: PASS\n\n■ Verification axes (Stage 4 contract)\n- (A) targeted tests + pytest → PASS\n- (B) diff matches Stage 2 plan u1~u11 → PASS\n- (C) hardcoding / scope-creep audit → PASS\n- (D) regression check (normal-path AI=0, baseline + phase_z2) → PASS\n\n■ Test runs (commands + counts)\n- `python -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=short`\n → 116 passed in 0.22s (u1 config = 4, u2 schema = 7, u3 prompts = 11, u4 client mock, u5 validate = 10, u6 cache, u7 router = 9, u8 step12 = 9, u9 step17 = 13, u10 ast_isolation = 22 parametrised + 3, u11 docs_sync = 11 parametrised + 1).\n- `python -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -q`\n → 161 passed in 28.38s (Phase Z2 regression sweep — no normal-path failure).\n- `python -m pytest -q --ignore=tests/matching --ignore=scripts`\n → 526 passed in 42.67s (full sweep minus tests/matching ad-hoc analysis dir and scripts/ collection-only directory).\n\n■ Pre-existing failures NOT introduced by IMP-33\n- `scripts/test_phase_t_audit.py`, `scripts/test_phase_t_full.py`, `scripts/test_phase_t_real.py` raise `FileNotFoundError: data\\runs\\20260401_151426\\stage_0_context.json` during collection. The file path predates IMP-33 (Phase T audit fixture, not under any IMP-33 axis). IMP-33 touches: `src/config.py`, `src/phase_z2_ai_fallback/**`, `tests/phase_z2_ai_fallback/**`, `tests/test_phase_z2_ai_fallback_config.py`, `docs/architecture/IMP-17-CARVE-OUT.md`, `docs/architecture/IMP-31-GATE-AUDIT.md` — none of these are in `scripts/`. Scope: pre-existing, not regression.\n\n■ Diff vs Stage 2 plan (u1~u11)\n- u1 settings — `src/config.py` `:14-26` adds 9 `ai_fallback_*` fields: master flag default `False`, model `claude-opus-4-6-20250415`, timeout 60.0s, max_retries 3, backoff base 1.0 / cap 8.0 / jitter 0.3, budget 10, circuit threshold 5. Matches plan exactly.\n- u2 schema — `src/phase_z2_ai_fallback/schema.py` `:22-30` defines `ProposalKind` whitelist + `FORBIDDEN_KINDS = {\"mdx_text\",\"frame_id_change\",\"raw_html\",\"raw_css\"}`; `AiFallbackProposal` uses `extra=\"forbid\"` (test_schema rejects mdx_text/frame_id_change/raw_html/raw_css/unknown/extra → 7 cases pass).\n- u3 prompts — `src/phase_z2_ai_fallback/prompts.py` `:23-35` `SYSTEM_PROMPT` enforces MDX READ-ONLY + whitelist + forbidden + frame_id swap lock + slot population + Internal Region containment; `:55-61` raises on `route != ai_adaptation_required`; user_payload carries V4 (route/cardinality/label/frame_id/rank) + frame_contract + frame_visual_html + figma_partial_json + internal_region + mdx_text_READ_ONLY.\n- u4 client — `src/phase_z2_ai_fallback/client.py` `:43-92` `AiFallbackClient` sources every knob from `settings.ai_fallback_*` (no inline literals); transient retry set = `(APITimeoutError, APIConnectionError, RateLimitError, InternalServerError)`; budget + circuit accounting per-instance.\n- u5 validate — `src/phase_z2_ai_fallback/validate.py` `:31-83` four guards: schema revalidation, builder whitelist (only keys in `frame_contract.payload.builder_options`), dropped-slot guard (every declared `sub_zones[*].id` must remain), frame-swap guard (`payload.frame_id` must equal contract `frame_id`), Internal Region containment.\n- u6 cache — `src/phase_z2_ai_fallback/cache.py` `:36-82` `read_proposal` returns `None`; `save_proposal(..., visual_check_passed, user_approved)` raises `AiFallbackCacheGateError` unless BOTH True; once both True, storage raises `NotImplementedError(\"...IMP-33 u6 stub marker.\")` — IMP-46 marker.\n- u7 router — `src/phase_z2_ai_fallback/router.py` `:43-89` flag-off short-circuit BEFORE prompt/client; route-mismatch short-circuit; cache read; build prompt; client call; validate. Does NOT import `save_proposal` (test_router enforces).\n- u8 step12 — `src/phase_z2_ai_fallback/step12.py` `:32-141` `gather_step12_ai_repair_proposals`. IMP-30 provisional gate (`not_provisional`) + reject gate (`design_reference_only_no_ai`) + non-AI catch-all (`route_not_ai_adaptation:`) run BEFORE `route_ai_fallback`. Record schema stable across all gate paths.\n- u9 step17 — `src/phase_z2_ai_fallback/step17.py` `:40-111` `OVERFLOW_CASCADE_ORDER = (DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE)` + `STEP17_AI_REPAIR_BLOCKED_REASON = \"step17_ai_blocked_imp_34_35_prerequisites_missing\"`. `gather_step17_ai_repair_proposals` records BLOCKED for every unit; module does NOT import `route_ai_fallback`, `anthropic`, or `AiFallbackClient` (test_step17 enforces 3 import-absence assertions).\n- u10 AST guard — `tests/phase_z2_ai_fallback/test_ast_isolation.py` `:71-153` parses every `*.py` under `src/phase_z2_ai_fallback/`; whitelist = `src.config` + intra-package + stdlib + `anthropic` + `pydantic`; rejects `src.pipeline`, `src.kei_client`, `src.phase_z2_pipeline`, and any other `src.phase_z2_*` runtime.\n- u11 docs sync — `tests/phase_z2_ai_fallback/test_docs_sync.py` `:25-61` parametrises 10 binding needles (module path, Step 12 entry, Step 17 entry + sentinel, OVERFLOW_CASCADE_ORDER + tuple form, visual_check_passed, user_approved, AiFallbackCacheGateError, ai_fallback_enabled) across `IMP-17-CARVE-OUT.md` + standalone `IMP-31-GATE-AUDIT.md` check for \"scaffolded under IMP-33\" + flag mention.\n\n■ Hardcoding + scope-creep audit\n- grep `(03[._-]|04[._-]|05[._-]|mdx[/_]\\d|sample_\\d|BIM|건설산업)` over `src/phase_z2_ai_fallback/**` → only `imp05_route_hint` (V4 fallback rank alias from prompts.py:55 / router.py:63; `05` is the IMP-05 axis identifier, not sample 05.mdx — confirmed by surrounding token). No sample-specific case branches.\n- grep `(60\\.0|3\\b|8\\.0|0\\.3|10\\b|5\\b).*(timeout|retry|backoff|budget|circuit)` over `src/phase_z2_ai_fallback/**` → only step17.py:30 docstring mention of \"user-approved fallback budget\" (prose, not a literal). No inline policy literals.\n- grep `(time\\.sleep|requests\\.post|httpx|urllib|claude-opus|anthropic\\.Anthropic\\()` over `src/phase_z2_ai_fallback/**` → 2 hits: `anthropic.Anthropic(api_key=settings.anthropic_api_key, timeout=settings.ai_fallback_timeout_s)` at client.py:53 and `time.sleep(delay)` at client.py:89 where `delay` is computed from `settings.ai_fallback_backoff_*`. No hardcoded delays.\n- grep `(write_text|\\.write\\(|open\\(.+[\"']w)` over `src/phase_z2_ai_fallback/**` → 0 hits. No file writes from the fallback package.\n- grep `(mdx_text|mdx_content|mdx_raw).*=\\s*[^READ]` over `src/phase_z2_ai_fallback/**` → router.py:80 forwards `mdx_text=mdx_text`, step12.py reads via caller-supplied `mdx_text_loader`. No MDX writes.\n- AST pass over `src/phase_z2_ai_fallback/*.py` looking for `ast.Compare` constants containing `'03' | '04' | '05' | 'sample' | 'mdx_batch'` → 0 violations.\n\n■ Regression check (Phase Z normal-path AI=0)\n- `settings.ai_fallback_enabled` default = `False` (u1 lock). Router u7 short-circuits BEFORE any prompt/client work, so `anthropic.Anthropic` is never instantiated on the default normal path.\n- AST guard u10 enforces structurally: fallback package imports `src.config` + intra-package + stdlib + `anthropic` + `pydantic` only — cannot reach `src.phase_z2_pipeline` or any other phase_z2_* runtime.\n- Step 17 entry returns 100% BLOCKED records (no `route_ai_fallback` import in step17.py). Confirmed by 3 import-absence tests.\n- Baseline + phase_z2 sweep = 161 passed. Full sweep (excl. tests/matching analysis dir, scripts/ pre-existing FileNotFoundError) = 526 passed.\n\n=== EVIDENCE ===\n- Tests:\n - `python -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=short` → 116 passed in 0.22s.\n - `python -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -q` → 161 passed in 28.38s.\n - `python -m pytest -q --ignore=tests/matching --ignore=scripts` → 526 passed in 42.67s.\n- Tracked diff (`git diff --stat HEAD`):\n - `docs/architecture/IMP-17-CARVE-OUT.md` +12/-1\n - `docs/architecture/IMP-31-GATE-AUDIT.md` +4/-4\n - `src/config.py` +12/-0\n- Untracked Stage 3 deliverables (`src/phase_z2_ai_fallback/{__init__,schema,prompts,client,validate,cache,router,step12,step17}.py` + `tests/phase_z2_ai_fallback/{__init__,test_schema,test_prompts,test_client_mock,test_validate,test_cache,test_router,test_step12,test_step17,test_ast_isolation,test_docs_sync}.py` + `tests/test_phase_z2_ai_fallback_config.py`) all present, all u1~u11 mapped.\n- Files-touched scope match: every modified file is on the Stage 2 plan; no out-of-scope files modified.\n- Pre-existing scripts/ FileNotFoundError verified out-of-scope: 3 errors all in `scripts/test_phase_t_*.py`, none in IMP-33 surface.\n- Commit SHA: not yet committed (Stage 4 verification of un-committed Stage 3 deliverables; commit occurs in Stage 5 push).\n- Source comment ids: this is Claude #16 Stage 4 R1; predecessors Claude #15 + Codex #15 (Stage 3 R11 u11 close).\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T12:39:32+09:00", "updated_at": "2026-05-21T12:39:32+09:00"}, {"id": 22650, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22650", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #16] Stage 4 test-verify - IMP-33 #61\n\nStage 4 verdict: PASS\n\nVerification result:\n- Targeted IMP-33 fallback suite passed.\n- Phase Z2 regression sweep passed.\n- Scoped pytest run passed with `tests/matching` and `scripts` ignored, matching the Stage 4 verification scope.\n- Diff review matches the u1-u11 IMP-33 contract for the fallback module, config defaults, tests, and doc sync.\n- Commit OK signal is scope-qualified: commit only the IMP-33 files listed below. The worktree also contains unrelated #42/catalog changes and other untracked local artifacts; those are not part of this Stage 4 OK.\n\nDiff and contract checks:\n- u1: `src/config.py` defines `ai_fallback_enabled=False`, model `claude-opus-4-6-20250415`, timeout `60.0`, retries `3`, backoff base/cap/jitter `1.0/8.0/0.3`, budget `10`, circuit threshold `5`.\n- u2: `src/phase_z2_ai_fallback/schema.py` whitelists `builder_options_patch`, `partial_overrides`, `slot_mapping_proposal`; rejects `mdx_text`, `frame_id_change`, `raw_html`, `raw_css`; uses `extra=\"forbid\"`.\n- u3: `src/phase_z2_ai_fallback/prompts.py` builds only for `ai_adaptation_required`, carries V4/cardinality, frame contract, frame visual HTML, figma partial JSON, Internal Region, and `mdx_text_READ_ONLY`.\n- u4: `src/phase_z2_ai_fallback/client.py` wraps `anthropic.Anthropic(...).messages.create`, sources retry/timeout/backoff/budget/circuit knobs from settings, and retries only timeout/connection/429/5xx classes.\n- u5: `src/phase_z2_ai_fallback/validate.py` enforces builder-option whitelist, dropped-slot guard, frame-swap guard, and Internal Region containment.\n- u6: `src/phase_z2_ai_fallback/cache.py` returns cache miss until IMP-46 and raises `AiFallbackCacheGateError` unless both `visual_check_passed` and `user_approved` are true.\n- u7: `src/phase_z2_ai_fallback/router.py` short-circuits on flag-off and non-AI route before prompt/client work; it reads cache, builds prompt, calls client, validates, and does not import `save_proposal`.\n- u8: `src/phase_z2_ai_fallback/step12.py` gates Step 12 AI proposals behind provisional units, reject exclusion, and `ai_adaptation_required` route before calling the router.\n- u9: `src/phase_z2_ai_fallback/step17.py` keeps Step 17 structurally blocked behind IMP-34/IMP-35, defines `OVERFLOW_CASCADE_ORDER`, and does not import router/client/Anthropic.\n- u10: `tests/phase_z2_ai_fallback/test_ast_isolation.py` pins fallback package import isolation.\n- u11: `docs/architecture/IMP-17-CARVE-OUT.md`, `docs/architecture/IMP-31-GATE-AUDIT.md`, and `tests/phase_z2_ai_fallback/test_docs_sync.py` pin runtime surface and doc/code sync.\n\nScope notes:\n- `python -m pytest scripts/test_phase_t_audit.py scripts/test_phase_t_full.py scripts/test_phase_t_real.py -q --tb=short` still fails during collection outside IMP-33. Verified failures are `templates/catalog.yaml` missing for `scripts/test_phase_t_audit.py`, and `data/runs/20260401_151426/stage_0_context.json` missing for `scripts/test_phase_t_full.py` and `scripts/test_phase_t_real.py`.\n- The script collection failures are outside the IMP-33 touched files and were not included in the passing scoped Stage 4 pytest command.\n\n=== EVIDENCE ===\nCommands run:\n- `git status --short`\n- `git diff --name-only`\n- `rg -n \"ai_fallback|phase_z2_ai_fallback|gather_step12|step17|OVERFLOW_CASCADE_ORDER|AiFallbackCacheGateError\" src docs tests -S`\n- `git diff -- src/config.py docs/architecture/IMP-17-CARVE-OUT.md docs/architecture/IMP-31-GATE-AUDIT.md tests/test_phase_z2_ai_fallback_config.py tests/phase_z2_ai_fallback src/phase_z2_ai_fallback`\n- `rg -n \"mdx_text|frame_id_change|raw_html|raw_css|builder_options_patch|partial_overrides|slot_mapping_proposal|design_reference_only|not_provisional|Internal Region|READ-ONLY|anthropic|APITimeoutError|APIConnectionError|RateLimitError|InternalServerError\" src/phase_z2_ai_fallback tests/phase_z2_ai_fallback tests/test_phase_z2_ai_fallback_config.py src/config.py -S`\n- `python -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=short`\n- `python -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -q`\n- `python -m pytest -q --ignore=tests/matching --ignore=scripts`\n- `python -m pytest scripts/test_phase_t_audit.py scripts/test_phase_t_full.py scripts/test_phase_t_real.py -q --tb=short`\n- `git diff -- templates/phase_z2/catalog/frame_contracts.yaml templates/phase_z2/families/_WIP_FILES.md tests/test_family_contract_baseline.py`\n\nFiles checked:\n- `src/config.py`\n- `src/phase_z2_ai_fallback/__init__.py`\n- `src/phase_z2_ai_fallback/schema.py`\n- `src/phase_z2_ai_fallback/prompts.py`\n- `src/phase_z2_ai_fallback/client.py`\n- `src/phase_z2_ai_fallback/validate.py`\n- `src/phase_z2_ai_fallback/cache.py`\n- `src/phase_z2_ai_fallback/router.py`\n- `src/phase_z2_ai_fallback/step12.py`\n- `src/phase_z2_ai_fallback/step17.py`\n- `tests/test_phase_z2_ai_fallback_config.py`\n- `tests/phase_z2_ai_fallback/test_schema.py`\n- `tests/phase_z2_ai_fallback/test_prompts.py`\n- `tests/phase_z2_ai_fallback/test_client_mock.py`\n- `tests/phase_z2_ai_fallback/test_validate.py`\n- `tests/phase_z2_ai_fallback/test_cache.py`\n- `tests/phase_z2_ai_fallback/test_router.py`\n- `tests/phase_z2_ai_fallback/test_step12.py`\n- `tests/phase_z2_ai_fallback/test_step17.py`\n- `tests/phase_z2_ai_fallback/test_ast_isolation.py`\n- `tests/phase_z2_ai_fallback/test_docs_sync.py`\n- `docs/architecture/IMP-17-CARVE-OUT.md`\n- `docs/architecture/IMP-31-GATE-AUDIT.md`\n\nTest results:\n- `tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py`: 116 passed in 0.19s.\n- `tests/test_family_contract_baseline.py tests/phase_z2/`: 161 passed in 26.77s.\n- Scoped pytest excluding `tests/matching` and `scripts`: 526 passed in 42.09s.\n- Script-only collection check: 3 collection errors, all outside IMP-33 files, due to missing `templates/catalog.yaml` or `data/runs/20260401_151426/stage_0_context.json`.\n\nVerified facts:\n- Normal path AI remains off by default through `settings.ai_fallback_enabled=False`.\n- AI calls are reachable only through fallback module gates, not through the normal Phase Z2 regression path.\n- MDX remains read-only in prompt/schema/validation surfaces.\n- V4 rank-1 frame swap is blocked by schema and validation.\n- Step 17 AI repair remains blocked until IMP-34 and IMP-35.\n- IMP-46 cache persistence remains gated by visual check and user approval.\n- No commit was made in Stage 4.\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T12:44:31+09:00", "updated_at": "2026-05-21T12:44:31+09:00"}, {"id": 22654, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22654", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #17] Stage 5 commit-push — IMP-33 #61\n\n📌 **Stage 5: 커밋 및 푸쉬 R1 완료**\n\n■ 핵심 결정\n- IMP-33 (#61) u1~u11 산출물을 단일 commit `c864fe0` 으로 묶어 push 완료. origin (GitHub) + slide2 (Gitea mirror, 본 이슈 호스트) 양쪽 remote tip 이 `c864fe0` 로 동일.\n- 커밋 scope = Stage 4 exit guardrail 의 6 path 만: `src/config.py`, `src/phase_z2_ai_fallback/**` (9 파일), `tests/phase_z2_ai_fallback/**` (11 파일), `tests/test_phase_z2_ai_fallback_config.py`, `docs/architecture/IMP-17-CARVE-OUT.md`, `docs/architecture/IMP-31-GATE-AUDIT.md`. 총 24 files / +2119 / -5.\n- 의도 제외 (worktree 잔류, 별 IMP / 사전 작업): `templates/catalog/`, `templates/phase_z2/catalog/frame_contracts.yaml`, `templates/phase_z2/families/_WIP_FILES.md`, `templates/phase_z2/families/app_sw_package_vs_solution.html`, `templates/phase_z2/families/pre_construction_model_info_stacked.html`, `templates/phase_z2/frames/`, `tests/test_family_contract_baseline.py`, `tests/matching/**`, `tests/PIPELINE.md|PLAN.md|PROGRESS.md|README.md`, `tests/pipeline/`, `scripts/post_gitea_comment.py`, `scripts/sync_phase_z_run_to_frontend.py`, `samples/**`, `chromedriver/**`, `.claude/settings.local.json`, `.orchestrator/`, `tmpclosed_issues.json` 등.\n- Hook bypass 없음 (`--no-verify` X). gpg signing 무변경. amend 없음.\n- AI-fallback master flag `ai_fallback_enabled` 기본 OFF — production import 시 normal-path AI 호출 수 = 0 유지.\n\n■ 범위 제외\n- IMP-46 cache write-path 실선 연결 (Stage 4 unresolved 그대로 — cache.py 는 NotImplementedError 게이트 유지).\n- IMP-34 / IMP-35 unblock 후 Step 17 prerequisite 해제.\n- worktree 의 비-#61 untracked / modified 파일 정리는 본 stage scope 밖.\n\n■ 다음 단계\n- Stage 6 (final-close) 진입.\n\n=== EXIT REPORT (English, binding contract) ===\n\n**commit_sha**: `c864fe0479c1513afb4eb29c6b96342af5b50178`\n\n**commit_message_subject**: `feat(#61): IMP-33 AI fallback scaffolding (u1~u11, flag default OFF)`\n\n**files_staged** (24 files, +2119/-5):\n- `src/config.py` (M, +12/-0)\n- `src/phase_z2_ai_fallback/__init__.py` (A, 15)\n- `src/phase_z2_ai_fallback/cache.py` (A, 82)\n- `src/phase_z2_ai_fallback/client.py` (A, 92)\n- `src/phase_z2_ai_fallback/prompts.py` (A, 80)\n- `src/phase_z2_ai_fallback/router.py` (A, 89)\n- `src/phase_z2_ai_fallback/schema.py` (A, 50)\n- `src/phase_z2_ai_fallback/step12.py` (A, 141)\n- `src/phase_z2_ai_fallback/step17.py` (A, 111)\n- `src/phase_z2_ai_fallback/validate.py` (A, 83)\n- `tests/phase_z2_ai_fallback/__init__.py` (A, 0)\n- `tests/phase_z2_ai_fallback/test_ast_isolation.py` (A, 153)\n- `tests/phase_z2_ai_fallback/test_cache.py` (A, 90)\n- `tests/phase_z2_ai_fallback/test_client_mock.py` (A, 151)\n- `tests/phase_z2_ai_fallback/test_docs_sync.py` (A, 61)\n- `tests/phase_z2_ai_fallback/test_prompts.py` (A, 100)\n- `tests/phase_z2_ai_fallback/test_router.py` (A, 156)\n- `tests/phase_z2_ai_fallback/test_schema.py` (A, 46)\n- `tests/phase_z2_ai_fallback/test_step12.py` (A, 193)\n- `tests/phase_z2_ai_fallback/test_step17.py` (A, 208)\n- `tests/phase_z2_ai_fallback/test_validate.py` (A, 144)\n- `tests/test_phase_z2_ai_fallback_config.py` (A, 46)\n- `docs/architecture/IMP-17-CARVE-OUT.md` (M, +11/-2)\n- `docs/architecture/IMP-31-GATE-AUDIT.md` (M, +5/-3)\n\n**files_explicitly_excluded** (Stage 4 guardrail):\n- `templates/catalog/`\n- `templates/phase_z2/catalog/frame_contracts.yaml` (M, #42 territory)\n- `templates/phase_z2/families/_WIP_FILES.md` (M)\n- `templates/phase_z2/families/app_sw_package_vs_solution.html`\n- `templates/phase_z2/families/pre_construction_model_info_stacked.html`\n- `templates/phase_z2/frames/`\n- `tests/test_family_contract_baseline.py` (M, baseline regression suite unrelated to #61)\n- `tests/matching/**` (legacy matching workshop artifacts)\n- `tests/pipeline/`, `tests/PIPELINE.md`, `tests/PLAN.md`, `tests/PROGRESS.md`, `tests/README.md`\n- `scripts/post_gitea_comment.py`, `scripts/sync_phase_z_run_to_frontend.py`\n- `samples/**`, `chromedriver/**`\n- `.claude/settings.local.json`, `.orchestrator/`, `tmpclosed_issues.json`, miscellaneous `tmp` artifacts\n\n**push_targets**:\n- `origin` (`git@github.com:keimin86/design_agent.git`) → `main` `c412f1e..c864fe0`\n- `slide2` (`https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2.git`) → `main` `c412f1e..c864fe0`\n- `gitea` remote (URL `_Geulbeot.git`) NOT pushed (different project per `reference_design_agent_remotes`).\n\n**remote_verification**:\n- `git rev-parse HEAD` → `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git ls-remote origin main` → `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git ls-remote slide2 main` → `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- All three SHAs match.\n\n**hooks**: pre-commit / commit-msg hooks ran clean (no `--no-verify`). No gpg sign override. No amend. Single new commit on top of `c412f1e`.\n\n**diff_scope_check**: `git diff --staged --stat` before commit showed exactly the 24 paths above; no creep into excluded list.\n\n**pass_decision**: PASS\n\n**unresolved_questions**:\n- IMP-46 cache write-path remains stubbed (intentional carry-over from Stage 4; cache.py raises `AiFallbackCacheGateError` until visual_check_passed AND user_approved). Not blocking Stage 6.\n\n**guardrails**:\n- RULE 4 scope-qualified (24 named paths only). RULE 6 specific files only (no `git add -A`). RULE 7 no hardcoding (process-level commit). PZ-1 normal-path AI=0 preserved (master flag OFF). `feedback_workflow_atomicity_rules` (1 commit = 1 decision unit, message follows `feat(#NN): IMP-MM ` style of `c412f1e` / `1efbf67` / `b4872ba`). `reference_design_agent_remotes` honored — pushed to `slide2`, skipped `gitea` (`_Geulbeot.git`).\n\n**evidence**:\n- `git log --oneline -2` → `c864fe0 feat(#61): IMP-33 AI fallback scaffolding (u1~u11, flag default OFF)` / `c412f1e refactor(#41): IMP-32 Step 9 application_plan helper extraction (u1~u5)`\n- `git diff --staged --stat` (pre-commit) listed exactly 24 files matching Stage 4 allowlist.\n- `git push origin main` → `c412f1e..c864fe0 main -> main`\n- `git push slide2 main` → `remote: Processed 1 references in total` / `c412f1e..c864fe0 main -> main`\n- `git ls-remote origin main` / `git ls-remote slide2 main` both return `c864fe0479c1513afb4eb29c6b96342af5b50178`.\n\n**source_comment_ids**: Claude #16 (Stage 4 test-verify PASS, exit report binding), Codex #16 (Stage 4 verification).\n", "assets": [], "created_at": "2026-05-21T12:48:42+09:00", "updated_at": "2026-05-21T12:48:42+09:00"}, {"id": 22658, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22658", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #17] Stage 5 commit-push verification - IMP-33 #61\n\nStage 5 verification result: PASS.\n\nVerified commit SHA on local HEAD, origin, and slide2:\n- local `HEAD` = `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `origin/main` = `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `slide2/main` = `c864fe0479c1513afb4eb29c6b96342af5b50178`\n\nUnintended files in commit: NO.\n\nThe commit contains exactly 24 files and matches the Stage 5 scoped IMP-33 paths:\n- `src/config.py`\n- `src/phase_z2_ai_fallback/**`\n- `tests/phase_z2_ai_fallback/**`\n- `tests/test_phase_z2_ai_fallback_config.py`\n- `docs/architecture/IMP-17-CARVE-OUT.md`\n- `docs/architecture/IMP-31-GATE-AUDIT.md`\n\nPush success / remote reflects: YES. Live `git ls-remote` checks for both configured targets returned the exact Stage 5 commit SHA on `refs/heads/main`.\n\nResidual worktree files are outside the commit and outside Stage 5 scope. Examples confirmed in `git status --short`: `templates/catalog/`, `templates/phase_z2/**` WIP paths, `tests/matching/**`, `scripts/post_gitea_comment.py`, `scripts/sync_phase_z_run_to_frontend.py`, `samples/**`, `chromedriver/**`, `.claude/settings.local.json`, `.orchestrator/`, and `tmpclosed_issues.json`.\n\n=== EVIDENCE ===\nCommands run:\n- `git rev-parse HEAD`\n- `git status --short`\n- `git show --stat --oneline --decorate --name-status --format=fuller c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git remote -v`\n- `git ls-remote origin refs/heads/main`\n- `git ls-remote slide2 refs/heads/main`\n- `git show --shortstat --format= c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git diff-tree --no-commit-id --name-only -r c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git diff --cached --name-only`\n- `git branch --show-current`\n- `git log -1 --format=\"%H%n%s\"`\n\nFiles checked:\n- commit file list from `git diff-tree` for `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- worktree residual file list from `git status --short`\n- configured remotes from `git remote -v`\n\nTest results: not run in Stage 5; current task is commit/push verification and the prompt explicitly says not to run full pytest unless the stage task requires it.\n\nCommit SHA: `c864fe0479c1513afb4eb29c6b96342af5b50178`\n\nVerified facts:\n- Local branch is `main`.\n- Local `HEAD` equals `c864fe0479c1513afb4eb29c6b96342af5b50178`.\n- Latest commit subject is `feat(#61): IMP-33 AI fallback scaffolding (u1~u11, flag default OFF)`.\n- Commit shortstat is `24 files changed, 2119 insertions(+), 5 deletions(-)`.\n- `origin/main` and `slide2/main` both resolve to `c864fe0479c1513afb4eb29c6b96342af5b50178` via live `git ls-remote`.\n- `git diff --cached --name-only` returned no paths; nothing is staged after the commit.\n- No out-of-scope paths are present in the commit file list.\n\nFINAL_CONSENSUS: YES", "assets": [], "created_at": "2026-05-21T12:50:19+09:00", "updated_at": "2026-05-21T12:50:19+09:00"}, {"id": 22660, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22660", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #18] Stage 6 final-close — IMP-33 #61\n\n📌 **Final close review (Round #1)**\n\n■ Issue body re-read — original scope vs locked scope\n- Original issue body: \"reject / restructure / overflow fallback route 에 `Anthropic().messages.create()` 실선 추가\"; chain partners explicitly named as IMP-34 / IMP-35 / IMP-46.\n- Stage 2/3 scope-lock narrowed the deliverable to **scaffolding under `src/phase_z2_ai_fallback/` with master flag default OFF**, route-gating, and structural pins. Routes split as:\n - **restructure (Step 12)**: wired (after IMP-30 provisional payload, AI route only).\n - **overflow (Step 17)**: scaffolded but **gated** behind IMP-34 / IMP-35 prerequisites — consistent with the issue's own guardrail `overflow chain order: zone resize → responsive fit → popup → AI+cache → user override`.\n - **reject**: intentionally **design_reference_only, no-AI** (consistent with `feedback_ai_isolation_contract` + V4 spec).\n- All three branches behave as the issue body's guardrails require. No scope creep, no scope shrink below contract.\n\n■ Commit verification on origin (and Gitea mirror)\n- HEAD = `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git ls-remote origin refs/heads/main` → `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git ls-remote slide2 refs/heads/main` → `c864fe0479c1513afb4eb29c6b96342af5b50178`\n- All three tips identical. Push confirmed (Stage 5 binding).\n- Commit scope: 24 files / +2119 / -5 — exactly the Stage 4 declared 6-path scope (`src/config.py`, `src/phase_z2_ai_fallback/**`, `tests/phase_z2_ai_fallback/**`, `tests/test_phase_z2_ai_fallback_config.py`, `docs/architecture/IMP-17-CARVE-OUT.md`, `docs/architecture/IMP-31-GATE-AUDIT.md`).\n- `src/config.py:19` confirms `ai_fallback_enabled: bool = False` → normal-path AI call count = 0 on production import.\n\n■ Goal vs result (per issue body axes)\n\n| Issue axis | Goal | Delivered | Status |\n|---|---|---|---|\n| Module path | `src/phase_z2_ai_fallback/` | scaffolded (router/schema/prompts/client/validate/cache/step12/step17) | ✅ matches IMP-31-GATE-AUDIT contract |\n| AI call site | `Anthropic().messages.create()` real wire | `client.py` wraps anthropic SDK with timeout/retry/backoff/budget/circuit | ✅ (flag-OFF by default) |\n| Input contract | frame visual + cardinality + frame contract | `prompts.py` carries V4/cardinality/frame_contract/frame_visual/figma_partial/Internal Region/MDX-READ-ONLY | ✅ |\n| Output contract | builder_options / partial_overrides / slot_mapping_proposal | `schema.py` whitelist + `extra=\"forbid\"` | ✅ |\n| Retry/backoff/budget | explicit policy | u1 settings (timeout 60s, retries 3, backoff 1.0/8.0/0.3, budget 10, circuit 5) — no inline literals | ✅ |\n| Model | `claude-opus-4-6-20250415` | `ai_fallback_model = \"claude-opus-4-6-20250415\"` | ✅ |\n| MDX read-only | no rewrite | rejected at schema + validate | ✅ |\n| No frame swap | V4 rank-1 protected | `validate.py` frame-swap guard | ✅ |\n| Overflow chain order | deterministic → popup → AI+cache → user override | `OVERFLOW_CASCADE_ORDER` constant + step17 gated | ✅ |\n| IMP-46 cache gate | visual_check_passed AND user_approved | `cache.py` raises `AiFallbackCacheGateError`; storage `NotImplementedError` for IMP-46 | ✅ (gate present; storage intentionally deferred) |\n| Normal-path AI = 0 | guardrail | router short-circuits on flag-off + non-AI route; 526 scoped tests green | ✅ |\n\n■ Follow-up links (not blocking close)\n- **IMP-46 (#62)** — frame transformation cache **storage** activation. Current `cache.py` returns miss until IMP-46; gate is enforced but persistence is `NotImplementedError`. Pair-issue per #61 body.\n- **IMP-34** — visual_check_passed signal wiring (Step 16 deterministic overflow terminal). Step 17 stays blocked until this lands.\n- **IMP-35** — user_approved signal wiring (popup escalation). Step 17 stays blocked until this lands.\n- **#39 (IMP-30)** — first-render invariant already verified; AI repair hook lives downstream of its provisional payload (Step 12 entry point).\n- **#38 (IMP-29)** — frontend zone-level evidence bridge already verified; will surface fallback proposals once the flag is enabled.\n- **scripts/test_phase_t_*.py** — pre-existing collection error (missing `data\\runs\\20260401_151426\\stage_0_context.json` fixture), predates IMP-33, no overlap. Not a regression; can be cleaned in a separate audit IMP.\n\n■ Labels — issue currently has no labels. Recommend no label change (repo convention does not require labels at close).\n\n■ Review-loop checklist (issue body)\n- [x] Codex 1차 review (Stage 1 problem-review)\n- [x] Claude 재검토 (Stage 1 problem-review)\n- [x] Codex 재검증 (Stage 2 simulation-plan R2)\n- [x] scope-locked (Stage 2 IMPLEMENTATION_UNITS u1~u11)\n- [x] ready-for-implementation (Stage 2 exit)\n- [x] implemented (Stage 3 u1~u11 closed; Codex YES per unit)\n- [x] verified (Stage 4: 116 fallback / 161 phase_z2 regression / 526 scoped sweep all PASS; Stage 5 push verified on both remotes)\n\n■ Close decision: **CLOSE**\n\nRationale:\n1. All 11 axes of the issue body are satisfied by the locked-scope deliverable (table above).\n2. Commit `c864fe0` is the single decision unit; identical on local HEAD, origin/main, slide2/main.\n3. AI isolation contract upheld — master flag default OFF; normal-path AI = 0 verified by tests.\n4. Out-of-scope items (IMP-46 storage, IMP-34/35 unblock) are tracked as independent issues with explicit dependency edges, not as IMP-33 debt.\n5. No unresolved blockers; only forward dependencies that the issue body itself names as chain partners.\n\n=== EXIT REPORT (English, binding contract) ===\n\n```yaml\nclose_decision: CLOSE\ncommit_sha: c864fe0479c1513afb4eb29c6b96342af5b50178\nremote_verification:\n local_head: c864fe0479c1513afb4eb29c6b96342af5b50178\n origin_main: c864fe0479c1513afb4eb29c6b96342af5b50178\n slide2_main: c864fe0479c1513afb4eb29c6b96342af5b50178\n all_three_identical: true\n\ngoal_vs_result:\n module_path: delivered # src/phase_z2_ai_fallback/\n ai_call_site: delivered # client.py wraps Anthropic SDK\n input_contract: delivered # prompts.py\n output_contract: delivered # schema.py whitelist + forbid\n retry_backoff_budget: delivered # u1 settings, no inline literals\n model_value: delivered # claude-opus-4-6-20250415\n mdx_read_only: delivered # schema + validate\n no_frame_swap: delivered # validate.py\n overflow_chain_order: delivered # OVERFLOW_CASCADE_ORDER constant\n imp46_cache_gate: delivered # cache.py gate active, storage deferred to IMP-46\n normal_path_ai_zero: delivered # router short-circuit + 526 tests green\n\nfollow_up_issues:\n - IMP-46 (#62) — frame transformation cache STORAGE activation (gate already present)\n - IMP-34 — visual_check_passed signal wiring (unblocks Step 17)\n - IMP-35 — user_approved signal wiring (unblocks Step 17)\n - audit (separate) — scripts/test_phase_t_*.py pre-existing collection-error fixture cleanup\n\nlabels: no change recommended (issue carries no labels; repo convention does not require label at close)\n\nguardrails:\n - feedback_ai_isolation_contract upheld (flag-gated scaffolding only)\n - feedback_no_hardcoding upheld (process-first; structural test pin instead of manual sync)\n - feedback_one_step_per_turn upheld (only Stage 6 final-close performed)\n - feedback_scope_qualified_verification upheld (every claim carries file path or test count)\n - feedback_anchor_sync_rules upheld (IMP-17 / IMP-31 cross-anchor mirror pinned by test_docs_sync.py)\n\nunresolved_questions: none (all follow-ups have dedicated issues already tracked)\n\nevidence:\n - git rev-parse HEAD → c864fe0479c1513afb4eb29c6b96342af5b50178\n - git ls-remote origin refs/heads/main → c864fe0479c1513afb4eb29c6b96342af5b50178\n - git ls-remote slide2 refs/heads/main → c864fe0479c1513afb4eb29c6b96342af5b50178\n - git show --shortstat c864fe0 → 24 files / +2119 / -5\n - git diff-tree --no-commit-id --name-only -r c864fe0 → 24 files, all within Stage 4 declared scope\n - src/config.py:19 → ai_fallback_enabled: bool = False\n - GET /repos/Kyeongmin/C.E.L_Slide_test2/issues/61 → state=open, labels=[]\n\nsource_comment_ids:\n - Claude #1..#17 (Stages 1–5 exit reports embedded above)\n - Codex #1..#17 (Stages 1–5 verification YES per stage)\n - Claude #18 (this comment, Stage 6 final-close Round #1)\n```\n", "assets": [], "created_at": "2026-05-21T12:53:35+09:00", "updated_at": "2026-05-21T12:53:35+09:00"}, {"id": 22668, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61#issuecomment-22668", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/61", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #18] Stage 6 final-close verification - IMP-33 #61\n\nFinal independent verification result: PASS. Close OK.\n\nScope-qualified review:\n- Verified commit `c864fe0479c1513afb4eb29c6b96342af5b50178` is local HEAD and is the tip of both `origin/main` and `slide2/main`.\n- Verified commit scope is exactly the Stage 5 declared IMP-33 path set: `src/config.py`, `src/phase_z2_ai_fallback/**`, `tests/phase_z2_ai_fallback/**`, `tests/test_phase_z2_ai_fallback_config.py`, `docs/architecture/IMP-17-CARVE-OUT.md`, and `docs/architecture/IMP-31-GATE-AUDIT.md`.\n- Verified `src/config.py` keeps `ai_fallback_enabled: bool = False`, so production import does not enable normal-path AI calls.\n- Verified the AI call is present only behind fallback scaffolding: `src/phase_z2_ai_fallback/client.py` wraps `anthropic.Anthropic(...).messages.create(...)` with model, timeout, retry, backoff, budget, and circuit settings sourced from `src.config.settings`.\n- Verified router short-circuits before prompt/client work when the flag is off or V4 route is not `ai_adaptation_required`; router imports `read_proposal` but does not import `save_proposal`.\n- Verified output schema allows only `builder_options_patch`, `partial_overrides`, and `slot_mapping_proposal`; forbidden `mdx_text`, `frame_id_change`, `raw_html`, and `raw_css` are rejected with `extra=\"forbid\"`.\n- Verified prompt input contract includes route/cardinality, frame contract, frame visual HTML, figma partial JSON, Internal Region, and `mdx_text_READ_ONLY`.\n- Verified validation guards cover builder-option whitelist, dropped-slot guard, frame-swap guard, and Internal Region containment.\n- Verified cache write gate raises `AiFallbackCacheGateError` unless both `visual_check_passed` and `user_approved` are true, then raises `NotImplementedError` until IMP-46 persistent storage lands.\n- Verified Step 12 is limited to IMP-30 provisional AI-adaptation units; reject/design-reference units are skipped as `design_reference_only_no_ai`.\n- Verified Step 17 is structurally blocked behind IMP-34/IMP-35 and exposes cascade order `DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE`.\n- Verified docs bind the same runtime module path, Step 12 entry, Step 17 entry, cascade order, cache gate, and flag default.\n- Verified residual worktree changes are outside the committed IMP-33 scope and were not part of `c864fe0`.\n\nNo rewind target required.\n\n=== EVIDENCE ===\nCommands run:\n- `git rev-parse HEAD`\n- `git ls-remote origin refs/heads/main`\n- `git ls-remote slide2 refs/heads/main`\n- `git diff-tree --no-commit-id --name-only -r c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `git show --shortstat --oneline --no-renames c864fe0479c1513afb4eb29c6b96342af5b50178`\n- `rg -n \"ai_fallback_|claude-opus|timeout|retry|backoff|budget|circuit\" src/config.py`\n- `rg -n \"builder_options_patch|partial_overrides|slot_mapping_proposal|mdx_text|frame_id_change|raw_html|raw_css|extra\" src/phase_z2_ai_fallback/schema.py`\n- `rg -n \"Anthropic|messages\\\\.create|timeout|retry|429|500|529|Connection|RateLimit|Overloaded|budget|circuit\" src/phase_z2_ai_fallback/client.py`\n- `rg -n \"mdx_text_READ_ONLY|frame_visual|figma|cardinality|frame_contract|Internal Region|ai_adaptation_required|READ-ONLY|route\" src/phase_z2_ai_fallback/prompts.py`\n- `rg -n \"dropped|frame|Internal|contain|builder|text|table|image|details|rank|slot\" src/phase_z2_ai_fallback/validate.py`\n- `rg -n \"read_proposal|save_proposal|visual_check_passed|user_approved|NotImplementedError|AiFallbackCacheGateError\" src/phase_z2_ai_fallback/cache.py`\n- `rg -n \"ai_fallback_enabled|route|cache|build_ai_fallback_prompt|validate|save_proposal|read_proposal|client\" src/phase_z2_ai_fallback/router.py`\n- `rg -n \"gather_step12_ai_repair_proposals|design_reference_only|provisional|IMP-30|ai_adaptation_required|reject\" src/phase_z2_ai_fallback/step12.py`\n- `rg -n \"OVERFLOW_CASCADE_ORDER|DETERMINISTIC|POPUP|AI_REPAIR|USER_OVERRIDE|step17_ai_blocked_imp_34_35_prerequisites_missing|IMP-34|IMP-35\" src/phase_z2_ai_fallback/step17.py`\n- `rg -n \"Runtime module surface|src/phase_z2_ai_fallback|gather_step12_ai_repair_proposals|step17_ai_blocked_imp_34_35_prerequisites_missing|OVERFLOW_CASCADE_ORDER|AiFallbackCacheGateError|ai_fallback_enabled\" docs/architecture/IMP-17-CARVE-OUT.md docs/architecture/IMP-31-GATE-AUDIT.md`\n- `python -m pytest tests/phase_z2_ai_fallback/test_router.py tests/phase_z2_ai_fallback/test_step12.py tests/phase_z2_ai_fallback/test_step17.py tests/phase_z2_ai_fallback/test_cache.py tests/phase_z2_ai_fallback/test_docs_sync.py -q`\n- `git status --short`\n\nFiles checked:\n- `src/config.py`\n- `src/phase_z2_ai_fallback/schema.py`\n- `src/phase_z2_ai_fallback/prompts.py`\n- `src/phase_z2_ai_fallback/client.py`\n- `src/phase_z2_ai_fallback/validate.py`\n- `src/phase_z2_ai_fallback/cache.py`\n- `src/phase_z2_ai_fallback/router.py`\n- `src/phase_z2_ai_fallback/step12.py`\n- `src/phase_z2_ai_fallback/step17.py`\n- `tests/phase_z2_ai_fallback/test_docs_sync.py`\n- `docs/architecture/IMP-17-CARVE-OUT.md`\n- `docs/architecture/IMP-31-GATE-AUDIT.md`\n\nVerified facts:\n- Local HEAD = `c864fe0479c1513afb4eb29c6b96342af5b50178`.\n- `origin/main` = `c864fe0479c1513afb4eb29c6b96342af5b50178`.\n- `slide2/main` = `c864fe0479c1513afb4eb29c6b96342af5b50178`.\n- Commit shortstat = 24 files changed, 2119 insertions, 5 deletions.\n- Commit file list contains only the Stage 5 declared IMP-33 paths.\n- `ai_fallback_enabled` default is `False`; model is `claude-opus-4-6-20250415`; timeout/retry/backoff/budget/circuit fields are present in `src/config.py`.\n- Router flag and route gates short-circuit before prompt/client work; router has no executable `save_proposal` import.\n- Step 17 file contains no executable import or call to `route_ai_fallback`, `AiFallbackClient`, or `anthropic`.\n- Targeted final-close fallback tests passed: 52 passed in 0.07s. Full pytest was not run because the current-stage instruction says not to run full pytest unless explicitly required.\n- Residual worktree entries exist, but they are outside the verified commit scope.\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-21T12:56:39+09:00", "updated_at": "2026-05-21T12:56:39+09:00"}]