Files
C.E.L_Slide_test2/.orchestrator/issues/61_comments_cache.json
T

1 line
246 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[{"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 = \"<latest stable opus 4.x>\"`, `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<N>` entry.\n\nRequirements (strict):\n- `=== IMPLEMENTATION_UNITS ===` block with at least one `- id: u<N>` entry\n- Each unit MUST include `tests: [<path>, ...]` (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: <int>\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 u1u6 + 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:<route>, 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:<hint>`) 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 <subject>` 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 15 exit reports embedded above)\n - Codex #1..#17 (Stages 15 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"}]