IMP-33 AI 호출 실선 wire up (frame-aware fallback) #61
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
관련 step: Step 9/12/17 — AI-assisted frame-aware adaptation 실제 호출 wire-up
source: #43 I3 (frame-aware AI 자동 보완 layer)
roadmap axis: R3 (AI 보정/재구성)
wave: 1 (실질 구동 도달 필수)
priority: ★ destination 본체
pair: IMP-46 (frame transformation cache) — 같이 작업
dependency: #17 (IMP-17 AI repair fallback infra carve-out) verified, #40 (IMP-31 AI-assisted frame-aware adaptation routing) verified, #42 (catalog 32) precondition
scope:
Anthropic().messages.create()실선 추가src/phase_z2_ai_repair.py(phase_z2_failure_router.py의 fallback hook 위 layer)claude-opus-4-6-20250415) 유지 가정 (변경 시 별 IMP)out of scope (별 IMP):
guardrail / validation:
feedback_ai_isolation_contract)feedback_phase_z_spacing_direction)feedback_no_hardcoding)cross-ref:
review loop:
IMP-33 AI ?? ?? wire up (frame-aware fallback)to IMP-33 AI 호출 실선 wire up (frame-aware fallback) [test]IMP-33 AI 호출 실선 wire up (frame-aware fallback) [test]to IMP-33 AI 호출 실선 wire up (frame-aware fallback)[Claude #1] Stage 1 problem-review — IMP-33
1. Source re-read + scope crosscheck
Closure semantics caveat — #40 (IMP-31) closed via doc-only audit (commits
182aa7c,e10ec36); it did NOT add runtime AI code. The audit itself explicitly statesRuntime AI module path = src/phase_z2_ai_fallback/ (not created this cycle)(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.2. Root cause — the activation gate vs. the issue scope (3-axis)
Axis A — 3-condition AND activation gate (carve-out boundary,
IMP-17-CARVE-OUT.md:25-31)Current state of the three conditions that must ALL hold before any runtime AI code lands in
phase_z2_*:c412f1e)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.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.grep -c 'template_id:' templates/phase_z2/catalog/frame_contracts.yaml= 32 (clear). IMP-05lookup_v4_match_with_fallbackrank-2/3 live since23d1b25(clear). But #42 (IMP-04b umbrella) still open — the parent close ceremony has not happened.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.Axis B — module path inconsistency (carve-out spec vs. issue body)
IMP-17-CARVE-OUT.mdimplicit +IMP-31-GATE-AUDIT.md:31, :56 explicitsrc/phase_z2_ai_fallback/(package / directory)src/phase_z2_ai_repair.py(single file)These 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.Axis C — hook surface vs. existing fallback layer
Issue body claims the AI hook should sit
phase_z2_failure_router.py 의 fallback hook 위 layer. But:src/phase_z2_failure_router.pytoday is classification + mapping only (phase_z2_failure_router.py:7 docstring "분류 + 매핑까지만"). It does NOT execute any action. No fallback hook exists on it.src/phase_z2_pipeline.py:2004 _attempt_salvage_chain(IMP-12 u8). This is the orchestrator that walkscross_zone_redistribute → glue_compression → font_step_compressionand would be the natural pre-position for an AI cascade extension.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 — perIMP-17-CARVE-OUT.md:16 "when retry router exhausts deterministic actions ... AND user-approved fallback budget remains, an AI proposal MAY be invoked".The 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.
Step 12 restructure path (
_imp05_route_hint(label) == "ai_adaptation_required"atsrc/phase_z2_pipeline.py:570-580) is a separate hook surface, also empty today — only deterministic emission ofcandidate_evidence[].route_hintexists (phase_z2_pipeline.py:664).3. Scope-lock proposal (binding boundaries — Stage 2 will refine)
(a) Behavior delta — what changes, what does NOT
route_hint="ai_adaptation_required"emitted incandidate_evidence(src/phase_z2_pipeline.py:664); IMP-30 path synthesizes provisional zone with raw MDX (src/phase_z2_pipeline.py:723-740)salvage_terminal_action∈ {layout_adjust, frame_reselect, details_popup_escalation, none} → exit withsalvage_passed=Falsenot_yet_wiredexit reason, no AI call (scope guard)phase_z2_failure_routermappingNEXT_ACTION_BY_FAILUREatsrc/phase_z2_failure_router.py:94-102)ai_proposal_rejected/ai_proposal_low_confidencedeferred to IMP-34/IMP-35APPLICATION_MODE_BY_V4_LABEL(src/phase_z2_pipeline.py:101)("layout_or_region_change", False, "human_review")("layout_or_region_change", False, "ai_fallback")when fallback budget allows;delegated_toswap is the only schema delta.human_reviewpreserved as the cascade terminalslide_status.overallenumai_fallback_attempts/ai_fallback_passesonlycaller="phase_z2_ai_fallback"for trace + accountingsrc/html_generator.py/src/kei_client.py/src/content_editor.py/src/pipeline.py(all Phase R'/Q)src/phase_z2_ai_fallback/package. Phase QEDITOR_PROMPT/ Kei-API endpoint NOT imported (IMP-17-CARVE-OUT.md:35-44, "영구 단절")claude-opus-4-6-20250415at 3 Kei sitessrc/config.pySettings (new fieldphase_z2_ai_fallback_model: str = "claude-opus-4-7-...") with .env override. Issue-body'sclaude-opus-4-6-20250415is 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(b) Output schema (the novel surface)
Issue 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 대상):Strict constraint :
builder_optionskeys MUST be validated against the builder's declared option keys (a new contract atsrc/phase_z2_mapper.pyor builder side); any key outside that set is rejected before apply.partial_overridesslot names MUST be in the frame contract's declared slots.slot_mappingindices 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.(c) Retry / backoff / timeout policy
Strawman (Stage 2 lock):
anthropic.APITimeoutError,anthropic.APIConnectionError, HTTP 429/5xx)src/config.py; prevents runaway in cascade scenarios)circuit_openorbudget_exhausted→ emitdelegated_to="human_review"(preserves IMP-12 cascade terminal semantics)(d) IMP-46 (cache) integration — pair-issue ordering
Issue 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:Ordering options (Stage 2 lock):
Claude #1 preference = P1 (no-op cache stub first, then incremental). Codex view in round 1차.
(e) Hook activation gating (the safe wiring shape)
Even 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:
This 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.
4. Guardrails (Stage 2 binding)
ai_adaptation_requiredANDphase_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:14-16IMP-17-CARVE-OUT.md:20,feedback_phase_z_spacing_directionbuilder_options/partial_overrides/slot_mappingONLY. No raw HTML, no CSS, no frame contract creation, no layout / zone topology selectionIMP-17-CARVE-OUT.md:21feedback_phase_z_spacing_directionIMP-17-CARVE-OUT.md:16, IMP-12 cascadesrc/config.pySettings (env-overridable). No sample-specific (mdx 03/04/05) branching insrc/phase_z2_ai_fallback/feedback_no_hardcodingtext_block/table/image/detailsslot 삭제 X. AI proposal cannot emit empty slot when the source unit has content for that slotEDITOR_PROMPT, Kei-API endpoint,content_editor.pyhttpx+SSE retry shape) 영구 단절 —src/phase_z2_ai_fallback/imports nothing fromsrc/content_editor.py/src/kei_client.pyIMP-17-CARVE-OUT.md:35-44phase_z2_ai_fallback_enableddefault False at HEAD merge. Behavior delta vs. HEAD = 0 unless explicitly enabled. CI default keeps flag offfeedback_auto_pipeline_first— noreview_requiredinjection between salvage cascade and AI fallback (auto pipeline = self-determining)feedback_auto_pipeline_firstcandidate_evidenceschema (IMP-05 L2 / IMP-29b4872baconsumer) unchanged. AI proposal trace lives under a NEW fieldai_fallback_tracein debug.json, NOT insidecandidate_evidenceneeds_adaptationstate (no regression to abort)IMP-31-GATE-AUDIT.md:50phase_z2_ai_fallbackpackagesrc/phase_z2_ai_fallback/(package, perIMP-17-CARVE-OUT.mdreservation). NOTsrc/phase_z2_ai_repair.py. Issue body wording corrected at scope-lock5. Implementation slicing sketch (Stage 2 plan input — NOT binding)
Wave-1 ordering (preferred):
U1 — Config + flag plumbing (src-touching, 0 behavior change)
src/config.pySettings :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.U2 —
src/phase_z2_ai_fallback/package skeleton__init__.pyexportspropose_for_unit(unit, ...) -> Optional[AiFallbackProposal].client.py: Anthropic client construction with retry/backoff/timeout/circuit-breaker primitives. NOEDITOR_PROMPT/ Kei import.schema.py:AiFallbackProposaldataclass + JSON schema for validation. Strict — no MDX text fields.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.validate.py: schema validator + builder option key whitelist check + slot name check + dropped-slot guard.not settings.phase_z2_ai_fallback_enabled.U3 — Step 12 hook (restructure path)
src/phase_z2_pipeline.pymapper / 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.ai_fallback_tracefield added (additive, optional).U4 — Step 17 cascade terminal hook (deferred until IMP-34/IMP-35 land)
_attempt_salvage_chain(src/phase_z2_pipeline.py:2004) : new terminal actionai_fallbackadded AFTERdetails_popup_escalation(IMP-35 landing).salvage_terminal_action="ai_fallback_not_yet_wired", no AI call in this path.U5 — IMP-46 stub integration
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).U6 — Tests
tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py— assert no import fromsrc/content_editor.py/src/kei_client.py/src/html_generator.py/src/pipeline.py.U7 — docs
IMP-17-CARVE-OUT.md: flip "design-only" → "active (gated byphase_z2_ai_fallback_enabled)" when Stage 2 confirms gate clear.IMP-31-GATE-AUDIT.md: update gate state table + A1/A2 verdicts.PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md: add IMP-33 + IMP-46 rows (currently missing — backlog stale relative to #61/#62 issue numbering).PHASE-Z-PIPELINE-STATUS-BOARD.md§5 : "runtime AI = Step 12 light_edit / restructure 1 곳만" → add Step 17 terminal entry.6. Open questions (Codex round 1차 review 대상)
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.src/phase_z2_ai_fallback/(per carve-out spec) vssrc/phase_z2_ai_repair.py(per issue body). Claude #1 preference = package per spec.not_yet_wiredguard 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 staleMISSINGstatus atphase_z2_failure_router.NEXT_ACTION_IMPLEMENTATION_STATUS:142-149._imp05_route_hintconsumer in the mapper loop (src/phase_z2_pipeline.py:3340~) AFTER IMP-30 provisional synthesis, vs at a new dedicated hook function inphase_z2_failure_router.py(rename module tophase_z2_router_and_fallback?). Claude #1 preference = mapper loop hook, keep failure_router pure classification.claude-opus-4-7-20260101from env header vsclaude-opus-4-6-...from Kei legacy), and what per-run AI call budget. Claude #1 strawman = latest opus 4.x, budget = 5. Codex view?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").=== EVIDENCE ===
src/phase_z2_failure_router.py— L1-318 (full module — classification + mapping only,NEXT_ACTION_IMPLEMENTATION_STATUSat L142-149 showslayout_adjust/frame_reselect= MISSING;cross_zone_redistribute/glue_compression/font_step_compression= IMPLEMENTED via IMP-12 u7).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).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).src/config.py— full file (3 fields: anthropic_api_key, kei_api_url, log_level).docs/architecture/IMP-17-CARVE-OUT.md— full file (carve-out scope, 3-condition AND gate, Phase Q 단절 contract).docs/architecture/IMP-31-GATE-AUDIT.md— full file (gate state table, issue-body axis verdict, future activation path declaration).docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md— L1-167 (Step 9 ⚠ partial, row (j) ❌ pending, §5 "runtime AI = Step 12 1 곳만").docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md— L60-72 (IMP-17 row, no IMP-33 / IMP-46 rows — backlog stale).docs/architecture/DORMANT-TRIGGERS.yaml— L60-73 (IMP-17 carve-out trigger description)..orchestrator/issues/40_stage_problem-review_exit.md,.orchestrator/issues/41_stage_final-close_exit.md(prior IMP-31/IMP-32 close evidence).grep -c "template_id:" templates/phase_z2/catalog/frame_contracts.yaml= 32 (gate condition #3 catalog clear).grep -n "Anthropic\|anthropic"insrc/(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-20250415at 3 kei_client sites only.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_repairreferenced only in #61 issue body.grep -n "_attempt_salvage_chain"= definedsrc/phase_z2_pipeline.py:2004, called L4976. Sole call site.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.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).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)..orchestrator/drafts/61_*= none prior (Round 1 — confirmed vials).rewind_target: 해당 없음 (YES 제안)
FINAL_CONSENSUS: YES
[Codex #1] Stage 1 problem-review - IMP-33
Stage 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.
3-axis status:
docs/architecture/IMP-17-CARVE-OUT.md:25-31requires 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.docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md:48says Step 9 is partial and V4 evidence is not integrated;:82-84says B4 v0 is catalog declaration order only;:126says render-path activation, B4 mapper integration, and V4 evidence integration are pending.template_id:entries, andsrc/phase_z2_pipeline.py:587-599confirmslookup_v4_match_with_fallbackis live and does not callcalculate_fit. However Gitea issue #42 is still open, so the umbrella closure/precondition is not complete.Runtime AI scope check:
src/phase_z2_ai_repair.pydoes not exist.src/phase_z2_ai_fallback/does not exist either, butdocs/architecture/IMP-31-GATE-AUDIT.md:31,:50, and:56explicitly define that future runtime module path assrc/phase_z2_ai_fallback/and say it was not created in that cycle.rgfound Anthropic calls in legacy/non-Phase-Z paths (src/kei_client.py,src/html_generator.py,src/pipeline.py), but noAnthropic().messages.create()/messages.create()runtime undersrc/phase_z2_*.src/phase_z2_failure_router.py:1-8is still classification + mapping only;:94-102maps 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 statesdetails_popup_escalationis not in the mapping at:37-38.Scope-lock verdict:
src/phase_z2_ai_repair.py, while the binding IMP-31 audit namessrc/phase_z2_ai_fallback/.=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'git status --shortrg -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 -Srg --files docs src templates tests | rg "(IMP-17|IMP-31|PIPELINE-STATUS|phase_z2_ai|failure_router|frame_contracts|feedback|phase_z2)"rg -c "^\s*template_id:" templates/phase_z2/catalog/frame_contracts.yamlSelect-String -Path 'docs\architecture\IMP-17-CARVE-OUT.md' -Pattern 'User GO|B4 frame_selection|catalog|AI 호출|Forbidden|Anthropic|normal path' -Context 2,3Select-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,3Select-String -Path 'docs\architecture\PHASE-Z-PIPELINE-STATUS-BOARD.md' -Pattern 'Step 9|B4|frame_selection|V4 evidence|pending|catalog declaration' -Context 2,3rg -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 -STest-Path -LiteralPath 'src\phase_z2_ai_repair.py'; Test-Path -LiteralPath 'src\phase_z2_ai_fallback'rg -n "Anthropic\(|messages\.create\(|claude-opus|phase_z2_ai_repair|phase_z2_ai_fallback" src docs tests templates -SFiles checked:
docs/architecture/IMP-17-CARVE-OUT.mddocs/architecture/IMP-31-GATE-AUDIT.mddocs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.mdsrc/phase_z2_pipeline.pysrc/phase_z2_failure_router.pysrc/phase_z2_router.pysrc/phase_z2_retry.pytemplates/phase_z2/catalog/frame_contracts.yamlTest results:
Verified facts:
c412f1e.templates/phase_z2/catalog/frame_contracts.yaml,templates/phase_z2/families/_WIP_FILES.md, andtests/test_family_contract_baseline.py.template_id:entries.src/phase_z2_ai_repair.pyandsrc/phase_z2_ai_fallback/are absent at HEAD.src/phase_z2_*.FINAL_CONSENSUS: YES
[Claude #2] Stage 2 simulation-plan — IMP-33 #61
=== IMPLEMENTATION_UNITS ===
summary: config flag plumbing — phase_z2_ai_fallback_enabled default OFF + model_id / timeout / max_retry / per_run_budget / circuit_threshold
files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]
tests: [tests/test_phase_z2_ai_fallback_config.py]
estimate_lines: 40
summary: package + AiFallbackProposal schema — 3 proposal_kind; rejects mdx_text / frame_id / html / css; trace fields
files: [src/phase_z2_ai_fallback/init.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]
tests: [tests/phase_z2_ai_fallback/test_schema.py]
estimate_lines: 50
summary: prompts.py — V4 evidence + frame_contract + Internal Region + cardinality + raw MDX read-only; no Phase Q
files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]
tests: [tests/phase_z2_ai_fallback/test_prompts.py]
estimate_lines: 50
summary: client.py — Anthropic.messages.create wrapper; exp backoff w/ jitter; circuit + budget; APITimeoutError/Connection/429/5xx retryable
files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]
tests: [tests/phase_z2_ai_fallback/test_client_mock.py]
estimate_lines: 50
summary: validate.py — schema + builder option whitelist + dropped-slot guard + frame-swap guard
files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]
tests: [tests/phase_z2_ai_fallback/test_validate.py]
estimate_lines: 50
summary: cache.py — IMP-46 no-op stub (P1 ordering); save=no-op until cache layer lands
files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]
tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]
estimate_lines: 35
summary: propose_for_unit() — gate → cache → prompt → client → validate; flag-off short-circuit
files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]
tests: [tests/phase_z2_ai_fallback/test_propose.py]
estimate_lines: 50
summary: Step 12 hook — AFTER IMP-30 provisional; applies to slot_payload; debug.json ai_fallback_trace
files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]
tests: [tests/phase_z2/test_imp33_step12_hook.py]
estimate_lines: 50
summary: Step 17 terminal — _attempt_salvage_chain ai_fallback after details_popup; BLOCKED_PENDING_IMP_34_35
files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]
tests: [tests/phase_z2/test_imp33_step17_guard.py]
estimate_lines: 45
summary: Phase Q isolation — no import from content_editor/kei_client/html_generator/pipeline
files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
estimate_lines: 35
summary: docs sync — IMP-17, IMP-31, STATUS-BOARD row(j), BACKLOG IMP-33 row
files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]
tests: []
estimate_lines: 50
Per-unit rationale:
Coverage: 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.
Out-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.
Rollback: flag (u1) master switch — False disables all sites. Full revert = git revert u1~u11. HEAD byte-identical default OFF.
=== EVIDENCE ===
rewind_target: 해당 없음 (YES 제안)
FINAL_CONSENSUS: YES
[Codex #2] Stage 2 simulation-plan review - IMP-33 #61
=== IMPLEMENTATION_UNITS ===
review:
source_comment: Claude #2 Stage 2 simulation-plan
units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]
implementation_units_block_exists: true
unit_size_rule:
max_estimate_lines_allowed: 50
max_files_allowed: 3
result: pass
completeness_result: fail
Per-unit rationale:
Out-of-scope notes:
Rollback strategy:
Claude #2 gives a flag-based rollback, but the plan must be rewound because coverage is incomplete before implementation starts.
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
c412f1ea75.rewind_target: stage_2_plan
FINAL_CONSENSUS: NO
[Claude #3] Stage 2 simulation-plan — IMP-33 #61 (R1 post-rewind)
=== IMPLEMENTATION_UNITS ===
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
files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]
tests: [tests/test_phase_z2_ai_fallback_config.py]
estimate_lines: 50
summary: AiFallbackProposal schema — proposal_kind∈{builder_options_patch,partial_overrides,slot_mapping_proposal}; rejects mdx_text/frame_id_change/raw_html/raw_css
files: [src/phase_z2_ai_fallback/init.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]
tests: [tests/phase_z2_ai_fallback/test_schema.py]
estimate_lines: 50
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
files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]
tests: [tests/phase_z2_ai_fallback/test_prompts.py]
estimate_lines: 50
summary: client — anthropic.Anthropic(timeout=60s); backoff/budget/circuit via u1; retryable=transient(timeout/connection/429/5xx); NO inline literals
files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]
tests: [tests/phase_z2_ai_fallback/test_client_mock.py]
estimate_lines: 50
summary: validate — schema+builder whitelist+dropped-slot guard(text/table/image/details)+frame-swap guard(V4 rank-1)+Internal Region containment
files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]
tests: [tests/phase_z2_ai_fallback/test_validate.py]
estimate_lines: 50
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")
files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]
tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]
estimate_lines: 50
summary: propose_for_unit — flag-off→route gate(restructure OR Step17)→cache.read→u3→u4→u5; save_proposal CALLER-driven
files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]
tests: [tests/phase_z2_ai_fallback/test_propose.py]
estimate_lines: 50
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[]
files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]
tests: [tests/phase_z2/test_imp33_step12_hook.py]
estimate_lines: 50
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'
files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]
tests: [tests/phase_z2/test_imp33_step17_guard.py]
estimate_lines: 50
summary: AST import-guard — phase_z2_ai_fallback/* NOT import content_editor/kei_client/html_generator/src.pipeline
files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
estimate_lines: 35
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
files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]
tests: []
estimate_lines: 50
Rationale (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.
Out-of-scope: IMP-46 storage; IMP-34/35; IMP-29; candidate_evidence; IMP-37; model upgrade.
Rollback: ai_fallback_enabled=False (u1) = master switch (u7/u8/u9 short-circuit). git revert u1~u11.
=== EVIDENCE ===
FINAL_CONSENSUS: YES
[Codex #3] Stage 2 simulation-plan review - IMP-33 #61
=== IMPLEMENTATION_UNITS ===
review:
source_comment: Claude #3 Stage 2 simulation-plan R1 post-rewind
units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]
implementation_units_block_exists: true
unit_size_rule:
max_estimate_lines_allowed: 50
max_files_allowed: 3
result: pass
completeness_result: pass
prior_rewind_gaps_closed:
frame_visual_and_figma_partial_input: u3
concrete_timeout_retry_backoff: u1 and u4
reject_route_disposition: u8
imp46_pass_and_user_ok_cache_gate: u6
step17_order_through_imp34_imp35_ai_cache: u9
Per-unit rationale:
Out-of-scope notes:
Rollback strategy:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
c412f1ea75.FINAL_CONSENSUS: YES
⚠️ [Orchestrator] Stage 2 FINAL_CONSENSUS: YES rejected: no
- id: u<N>entry.Requirements (strict):
=== IMPLEMENTATION_UNITS ===block with at least one- id: u<N>entrytests: [<path>, ...](NOTtests: [])Example:
=== IMPLEMENTATION_UNITS ===
summary: ...
files: [...]
tests: [tests/.../test_xxx.py]
estimate_lines:
[Claude #4] Stage 2 simulation-plan — IMP-33 #61 (R2)
=== IMPLEMENTATION_UNITS ===
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
files: [src/config.py, tests/test_phase_z2_ai_fallback_config.py]
tests: [tests/test_phase_z2_ai_fallback_config.py]
estimate_lines: 50
summary: AiFallbackProposal schema — proposal_kind∈{builder_options_patch,partial_overrides,slot_mapping_proposal}; rejects mdx_text/frame_id_change/raw_html/raw_css
files: [src/phase_z2_ai_fallback/init.py, src/phase_z2_ai_fallback/schema.py, tests/phase_z2_ai_fallback/test_schema.py]
tests: [tests/phase_z2_ai_fallback/test_schema.py]
estimate_lines: 50
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
files: [src/phase_z2_ai_fallback/prompts.py, tests/phase_z2_ai_fallback/test_prompts.py]
tests: [tests/phase_z2_ai_fallback/test_prompts.py]
estimate_lines: 50
summary: client — anthropic.Anthropic; timeout/retry/backoff/budget/circuit via u1; retryable=transient(timeout/connection/429/5xx); NO inline literals
files: [src/phase_z2_ai_fallback/client.py, tests/phase_z2_ai_fallback/test_client_mock.py]
tests: [tests/phase_z2_ai_fallback/test_client_mock.py]
estimate_lines: 50
summary: validate — schema+builder whitelist+dropped-slot guard(text/table/image/details)+frame-swap guard(V4 rank-1)+Internal Region containment
files: [src/phase_z2_ai_fallback/validate.py, tests/phase_z2_ai_fallback/test_validate.py]
tests: [tests/phase_z2_ai_fallback/test_validate.py]
estimate_lines: 50
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")
files: [src/phase_z2_ai_fallback/cache.py, tests/phase_z2_ai_fallback/test_cache_stub.py]
tests: [tests/phase_z2_ai_fallback/test_cache_stub.py]
estimate_lines: 50
summary: propose_for_unit — flag-off→route gate(restructure|Step17)→cache.read→u3→u4→u5; save_proposal CALLER-driven
files: [src/phase_z2_ai_fallback/propose.py, tests/phase_z2_ai_fallback/test_propose.py]
tests: [tests/phase_z2_ai_fallback/test_propose.py]
estimate_lines: 50
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[]
files: [src/phase_z2_pipeline.py, tests/phase_z2/test_imp33_step12_hook.py]
tests: [tests/phase_z2/test_imp33_step12_hook.py]
estimate_lines: 50
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
files: [src/phase_z2_pipeline.py, src/phase_z2_failure_router.py, tests/phase_z2/test_imp33_step17_guard.py]
tests: [tests/phase_z2/test_imp33_step17_guard.py]
estimate_lines: 50
summary: AST import-guard — phase_z2_ai_fallback/* NOT import content_editor/kei_client/html_generator/src.pipeline
files: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
tests: [tests/orchestrator_unit/test_phase_z2_ai_fallback_isolation.py]
estimate_lines: 35
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
files: [docs/architecture/IMP-17-CARVE-OUT.md, docs/architecture/IMP-31-GATE-AUDIT.md, docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md]
tests: []
estimate_lines: 50
Rationale (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.
Out-of-scope: IMP-46 storage; IMP-34/35; IMP-29; IMP-37; model upgrade.
Rollback: u1 ai_fallback_enabled=False = master switch. git revert u11→u1.
=== EVIDENCE ===
c412f1eFINAL_CONSENSUS: YES
[Codex #4] Stage 2 simulation-plan review - IMP-33 #61
=== IMPLEMENTATION_UNITS ===
review:
source_comment: Claude #4 Stage 2 simulation-plan R2
units_checked: [u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]
implementation_units_block_exists: true
unit_size_rule:
max_estimate_lines_allowed: 50
max_files_allowed: 3
result: pass
completeness_result: pass
issue_axes_covered:
fallback_only_ai_call: u1, u4, u7, u8, u9
module_path: u2, u3, u4, u5, u6, u7, u10, u11
input_contract: u3
output_contract: u2, u5
retry_timeout_backoff: u1, u4
model_value: u1
mdx_read_only: u2, u3, u5
no_frame_swap: u5, u9
overflow_chain_order: u9
imp46_cache_gate: u6, u7
normal_path_ai_zero: u1, u7, u8, u10
Per-unit rationale:
Out-of-scope notes:
Rollback strategy:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
c412f1ea75.FINAL_CONSENSUS: YES
[Claude #5] Stage 3 code-edit — IMP-33 #61 (R1 u1)
Executing unit: u1
■ unit_executed
■ files_changed
■ diff_summary
src/config.py— added 9 fields toSettingsunder an IMP-33 u1 marker comment, all fallback-only policy knobs sourced from Stage 2 plan:ai_fallback_enabled: bool = Falseai_fallback_model: str = "claude-opus-4-6-20250415"ai_fallback_timeout_s: float = 60.0ai_fallback_max_retries: int = 3ai_fallback_backoff_base_s: float = 1.0ai_fallback_backoff_cap_s: float = 8.0ai_fallback_backoff_jitter: float = 0.3ai_fallback_budget_per_run: int = 10ai_fallback_circuit_breaker_threshold: int = 5tests/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).■ verification (this unit only, scope-qualified)
python -m pytest tests/test_phase_z2_ai_fallback_config.py -q→ 4 passed in 0.25s.■ guardrail_checks
■ remaining_units
■ follow_up_issue_candidates
■ next_action_proposal (informational only; not executed this turn)
src/phase_z2_ai_fallback/per Stage 1 audit-binding (IMP-31-GATE-AUDIT). No commit / push performed this turn (Stage 3 contract).rewind_target: continue_same
[Codex #5] Stage 3 code-edit review - IMP-33 #61 (R1 u1)
unit_verified: u1
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u2, u3, u4, u5, u6, u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #6] Stage 3 code-edit — IMP-33 #61 (R2 u2)
Executing unit: u2
■ unit_executed
■ files_changed
tests/phase_z2/__init__.pyconvention)■ diff_summary
src/phase_z2_ai_fallback/__init__.py— package init at the path locked by IMP-31-GATE-AUDIT (src/phase_z2_ai_fallback/). Re-exportsAiFallbackProposal,ProposalKind. Docstring records the fallback-only contract (feedback_ai_isolation_contract).src/phase_z2_ai_fallback/schema.py— Pydantic v2 modelAiFallbackProposalwith:ProposalKind(str-Enum) ={builder_options_patch, partial_overrides, slot_mapping_proposal}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 raisesValidationErrorbefore Enum coercion.model_config = ConfigDict(extra="forbid")— blocks AI from smuggling forbidden fields (e.g.raw_html=...) alongside a validproposal_kind.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).tests/phase_z2_ai_fallback/__init__.py— empty package marker.tests/phase_z2_ai_fallback/test_schema.py— 9 tests:proposal_kindstrings accepted (3 tests)ValidationError(4 tests)extra="forbid"blocksraw_html=...smuggled alongside a valid kind (1 test)■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_schema.py -q→ 9 passed in 0.04s.from src.phase_z2_ai_fallback import AiFallbackProposal, ProposalKindsucceeds;list(ProposalKind)returns the 3 whitelisted members.src/phase_z2_pipeline*.py, no Anthropic import, nomessages.create()call → normal-path AI call count unchanged.■ guardrail_checks
feedback_ai_isolation_contract: preserved — schema explicitly forbidsmdx_text(MDX read-only), so even when wired later it cannot rewrite MDX.feedback_phase_z_spacing_direction: preserved — schema forbidsframe_id_change, protecting V4 rank-1 (u5 will reinforce at validate layer).proposal_kindwhitelist is policy, not sample data.tests/phase_z2_ai_fallback/__init__.pypackage marker is a 0-byte pytest convention (mirrorstests/phase_z2/__init__.py) and carries no logical change. Flagging transparently.feedback_one_step_per_turn: only u2 executed; stopping here.■ remaining_units
■ follow_up_issue_candidates
■ next_action_proposal (informational only; not executed this turn)
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).rewind_target: continue_same
[Codex #6] Stage 3 code-edit review - IMP-33 #61 (R2 u2)
unit_verified: u2
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u3, u4, u5, u6, u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #7] Stage 3 code-edit — IMP-33 #61 (R3 u3)
Executing unit: u3
■ unit_executed
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 raisesValueErrorwhen V4 route ≠ai_adaptation_required.■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/prompts.py:V4_ROUTE_AI_ADAPTATION = "ai_adaptation_required"(mirrorssrc/phase_z2_pipeline.py:575_IMP05_ROUTE_HINTS["restructure"]).SYSTEM_PROMPT(module constant) — composes:feedback_ai_isolation_contract).AiFallbackProposal(u2).{builder_options_patch, partial_overrides, slot_mapping_proposal}fromProposalKind(no hardcoded duplicate; sourced from u2 module).{mdx_text, frame_id_change, raw_html, raw_css}fromFORBIDDEN_KINDS(single source of truth).frame_idchange (V4 rank-1 protected,feedback_phase_z_spacing_direction).build_ai_fallback_prompt(*, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text) -> dict[str, str]:v4_result.route(or aliasimp05_route_hint); raisesValueErrormentioningV4_ROUTE_AI_ADAPTATIONif not equal. This is the prompt-layer enforcement of PZ-1 (AI=0 normal path).{"system": SYSTEM_PROMPT, "user": json.dumps(payload, ensure_ascii=False)}.{"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).cardinalityorcardinality_signature(V4 caller compatibility).messages.create()call, no filesystem I/O (caller supplies loaded artifacts) — u4 owns client wiring.tests/phase_z2_ai_fallback/test_prompts.py— 11 tests:ProposalKindvalues.FORBIDDEN_KINDS.frame_id(swap lock).build_ai_fallback_promptreturns exactly the{system, user}shape andsystem == SYSTEM_PROMPT._READ_ONLY(asserts"mdx_text" not in payloadto confirm there is no writable MDX alias).direct_render,deterministic_minor_adjustment,design_reference_only, missing-route — all raiseValueErrormatchingV4_ROUTE_AI_ADAPTATION.cardinality_signaturealias is read whencardinalityis absent.■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_prompts.py -q→ 11 passed in 0.02s.python -m pytest tests/phase_z2_ai_fallback/ -q→ 20 passed in 0.03s (schema 9 + prompts 11).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.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.src/phase_z2_pipeline*.py; normal-path AI call count unchanged.■ guardrail_checks
ai_adaptation_required; raises before constructing prompt otherwise. No call-site introduced yet (u7 wires).feedback_ai_isolation_contract: preserved — MDX text is explicitly suffixed_READ_ONLYand the system prompt declares MDX is read-only.feedback_phase_z_spacing_direction: preserved — system prompt forbidsframe_idchange; u5 will reinforce at the validator.ProposalKindandFORBIDDEN_KINDS(single source of truth in u2). No sample-specific MDX/frame literal in this module.feedback_one_step_per_turn: only u3 executed; stopping here. u4 NOT touched.■ remaining_units
■ follow_up_issue_candidates
■ next_action_proposal (informational only; not executed this turn)
src/phase_z2_ai_fallback/client.pyper 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 u1Settings(no inline literals; u4 test asserts this). No commit / push performed this turn (Stage 3 contract).rewind_target: continue_same
[Codex #7] Stage 3 code-edit review - IMP-33 #61 (R3 u3)
unit_verified: u3
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u4, u5, u6, u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #8] Stage 3 code-edit — IMP-33 #61 (R4 u4)
Executing unit: u4
■ unit_executed
src/phase_z2_ai_fallback/client.py). Callsanthropic.Anthropic.messages.createwith 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.■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/client.py:src/phase_z2_ai_fallback/, Stage 1 binding)._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)._MAX_OUTPUT_TOKENS = 4096— Anthropic API requirement (not a policy knob), kept as module constant so it is not a per-call literal.AiFallbackBudgetExceeded(RuntimeError)andAiFallbackCircuitOpen(RuntimeError)— dedicated exception classes for caller routing (router u7 will branch on these).AiFallbackClient(dataclass) — stateful per-run client; holds_calls,_consecutive_failures; constructsanthropic.Anthropic(api_key=settings.anthropic_api_key, timeout=settings.ai_fallback_timeout_s)in__post_init__when no client is injected (test-friendly).request_proposal(prompt: dict[str, str]) -> AiFallbackProposal:_calls >= settings.ai_fallback_budget_per_run→AiFallbackBudgetExceeded._consecutive_failures >= settings.ai_fallback_circuit_breaker_threshold→AiFallbackCircuitOpen.settings.ai_fallback_max_retries + 1attempts.delay = min(cap, base * 2**attempt) + jitterwhere every parameter is read fromsettings.ai_fallback_backoff_{base_s,cap_s,jitter}(u1). NO inline literals._consecutive_failures = 0, parse JSON, validate via u2AiFallbackProposal.model_validate(...)and return._consecutive_failures, re-raise the last transient error.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.tests/phase_z2_ai_fallback/test_client_mock.py— 8 tests covering every axis the Stage 2 plan listed for u4:test_success_returns_validated_proposal— happy path returnsAiFallbackProposalwhoseproposal_kindmatches the u2 whitelist.test_call_uses_settings_model— verifiesmessages.create(...).kwargs["model"] == settings.ai_fallback_model(asserts the model value comes from Settings, not an inline string).test_transient_retries_then_succeeds—[APITimeoutError, APIConnectionError, ok]→ succeeds on attempt 3,messages.create.call_count == 3.test_retries_exhausted_raises_last_transient— withmax_retries=1, twoAPITimeoutError→ raisesAPITimeoutError,call_count==2,_consecutive_failures==1.test_non_transient_not_retried— custom_NonTransientexception (NOT in_TRANSIENT_ERRORS) → raised immediately,call_count==1.test_budget_exceeded—budget_per_run=1→ secondrequest_proposal(...)raisesAiFallbackBudgetExceeded.test_circuit_breaker_opens—circuit_breaker_threshold=1,max_retries=0→ first call raises transient (_consecutive_failures→ 1), second call raisesAiFallbackCircuitOpen.test_backoff_uses_settings— withbase=0.25, cap=0.5, jitter=0.0, max_retries=1, monkeypatchedtime.sleeprecords exactly[0.25]. Proves the backoff formula reads from Settings (zero inline literals would otherwise change this value)._no_real_sleep(zerostime.sleepso retry tests are fast) and_restore_settings(snapshots+restoressettings.model_dump()so monkeypatched values do not leak across tests / impact other suites).■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_client_mock.py -q→ 8 passed in 0.05s.python -m pytest tests/phase_z2_ai_fallback/ -q→ 28 passed in 0.04s (u2: 9 + u3: 11 + u4: 8).client.pyreads fromsettings.ai_fallback_*. The only numeric literals are_MAX_OUTPUT_TOKENS = 4096(Anthropic API max_tokens — not a policy knob, kept as module constant) and2 ** 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.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.src/phase_z2_pipeline*.py, no edits tosrc/phase_z2_failure_router.py, no router wiring → normal-path AI call count remains 0.■ guardrail_checks
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).feedback_phase_z_spacing_direction/ V4 rank-1 lock: preserved — u4 does not change frame selection; output is validated by u2 schema (forbiddenframe_id_change) and will be re-checked by u5 validate.settings.ai_fallback_*. Thetest_backoff_uses_settingstest is the regression guard against future drift back to inline literals.feedback_one_step_per_turn: only u4 executed; stopping here. No edits beyondclient.py+ its test file.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.■ remaining_units
■ follow_up_issue_candidates
_MAX_OUTPUT_TOKENSshould 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.■ next_action_proposal (informational only; not executed this turn)
rewind_target: continue_same
[Codex #8] Stage 3 code-edit review - IMP-33 #61 (R4 u4)
unit_verified: u4
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u5, u6, u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #9] Stage 3 code-edit — IMP-33 #61 (R5 u5)
Executing unit: u5
■ unit_executed
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 inframe_contract.payload.builder_options); dropped-slot guard (PARTIAL_OVERRIDES / SLOT_MAPPING_PROPOSAL must keep every declaredsub_zones[*].idpopulated); frame-swap guard (nopayload.frame_idmutation; V4 rank-1 protected); Internal Region containment (payload.region_idmust match declaredinternal_region.idwhen supplied).■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/validate.py:src/phase_z2_ai_fallback/, Stage 1 binding).AiFallbackValidationError(ValueError)— dedicated exception class so caller (u7 router) can branch precisely (separate fromAiFallbackBudgetExceeded/AiFallbackCircuitOpendefined in u4)._SLOT_KINDS = (ProposalKind.PARTIAL_OVERRIDES, ProposalKind.SLOT_MAPPING_PROPOSAL)— kinds that imply slot placement (vs. BUILDER_OPTIONS_PATCH which only mutates builder knobs).validate_proposal(proposal, *, frame_contract, internal_region=None) -> None: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+...").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).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.PARTIAL_OVERRIDES/SLOT_MAPPING_PROPOSAL,payload["slots"]MUST be a dict and MUST contain every declaredsub_zones[*].id. Text/table/image/details slots cannot disappear (matches u3 SYSTEM_PROMPT rule 6).internal_regionis supplied ANDpayload["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).messages.create()call, no filesystem I/O — pure validation.tests/phase_z2_ai_fallback/test_validate.py— 10 tests covering every guard the Stage 2 plan listed for u5:test_builder_options_patch_accepts_whitelisted_keys— patchingitem_parser(declared key) passes.test_builder_options_patch_rejects_unknown_key— patchingpadding_px(undeclared) raises with "builder whitelist".test_partial_overrides_requires_all_declared_slots— slots{pillar_1, pillar_2}(missingpillar_3) raises "dropped-slot guard".test_partial_overrides_with_all_slots_passes— all 3 pillars present → passes.test_slot_mapping_proposal_requires_slots_dict—slots=[](not a dict) raises "dropped-slot guard".test_frame_swap_guard_rejects_mismatched_frame_id—payload.frame_id=9999≠ contract1171281190raises "frame-swap guard".test_frame_swap_guard_accepts_matching_frame_id— matching frame_id alongside completeslotspasses.test_internal_region_containment_rejects_mismatch—payload.region_id="zone_bottom.region_x"≠internal_region.id="zone_top.region_a"raises "Internal Region".test_internal_region_containment_accepts_match— matching region_id passes.test_internal_region_check_skipped_when_no_region_supplied—internal_region=Nonedefaults skip the check (caller-driven)._FRAME_CONTRACTmirrors F13 (three_parallel_requirements) fromtemplates/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.■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_validate.py -q→ 10 passed in 0.03s.src/phase_z2_ai_fallback/validate.pyper IMP-31-GATE-AUDIT (Stage 1 binding).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.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.■ guardrail_checks
messages.create()grep across new files = 0.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.■ remaining_units
■ follow_up_issue_candidates
imp46_cache_miss=truebefore AI call) are intentionally deferred to u7 (router) per Stage 2 unit layering.■ next_action_proposal (informational only; not executed this turn)
rewind_target: continue_same
[Codex #9] Stage 3 code-edit review - IMP-33 #61 (R5 u5)
unit_verified: u5
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u6, u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #10] Stage 3 code-edit — IMP-33 #61 (R6 u6)
Executing unit: u6
■ unit_executed
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 returnsNone(caller exercises the cache-miss path until IMP-46 lands).save_proposal(key, proposal, *, visual_check_passed, user_approved)raisesAiFallbackCacheGateErrorif EITHER gate is False (no write attempt). When BOTH gates are True, the storage layer raisesNotImplementedError("IMP-46 ...")— the explicit IMP-46 marker per Stage 2 plan.■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/cache.py:src/phase_z2_ai_fallback/, Stage 1 binding).AiFallbackCacheGateError(RuntimeError)— dedicated exception class so the caller (u7 router) can branch precisely (separate fromAiFallbackBudgetExceeded/AiFallbackCircuitOpendefined in u4 andAiFallbackValidationErrordefined in u5).read_proposal(key: str) -> AiFallbackProposal | None:ValueError(defence-in-depth; cache keys cannot be silently falsy).Noneunconditionally — IMP-33 ships without a persistent backend.save_proposal(key, proposal, *, visual_check_passed, user_approved) -> None:ValueError.AiFallbackProposalobject →TypeError(the cache is u2-typed; callers cannot smuggle dicts).visual_check_passed=False→AiFallbackCacheGateError(no write attempt).user_approved=False→AiFallbackCacheGateError(no write attempt).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").anthropicis not referenced; only the docstring mentions it negatively). NOmessages.create()call. NO MDX I/O. Pure proposal bookkeeping perfeedback_ai_isolation_contract.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:test_read_proposal_returns_none_for_any_key— stub contract.test_read_proposal_rejects_empty_key— defence-in-depth.test_save_rejects_when_visual_check_failed— gate 1 (raisesAiFallbackCacheGateError, message mentionsvisual_check_passed).test_save_rejects_when_user_not_approved— gate 2 (raises, message mentionsuser_approved).test_save_rejects_when_both_gates_false— gate combination.test_save_raises_not_implemented_when_both_gates_pass— IMP-46 marker (NotImplementedError, message mentionsIMP-46).test_save_rejects_empty_key— defence-in-depth.test_save_rejects_non_proposal_object— u2 typing.test_gate_error_is_not_notimplementederror— the two failure surfaces (AiFallbackCacheGateErrorvsNotImplementedError) remain disjoint, so u7 router can branch on them independently.■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_cache.py -q→ 9 passed in 0.11s.rg "^import anthropic|^from anthropic" src/phase_z2_ai_fallback/cache.py→ no matches. The onlyAnthropicsubstring in the file is the docstring sentence "No Anthropic import" (negation).src/phase_z2_pipeline*.py, no Anthropic import, nomessages.create()call → normal-path AI call count unchanged.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.■ guardrail_checks
feedback_ai_isolation_contract: preserved — no MDX read/write; cache stores u2 proposals (which forbidmdx_text).feedback_artifact_status_naming: preserved — gate violations are LOUD (AiFallbackCacheGateError), not silent skipped writes. Storage absence is also loud (NotImplementedError("IMP-46 ...")).feedback_phase_z_spacing_direction: preserved — cache stores proposals as-is; frame-swap guard is upstream at u5 validator.feedback_one_step_per_turn: only u6 executed; stopping here.■ remaining_units
AiFallbackBudgetExceeded/AiFallbackCircuitOpen/AiFallbackValidationError/AiFallbackCacheGateErrorfor branching)■ follow_up_issue_candidates
NotImplementedError("IMP-46 ...")marker on purpose.)■ next_action_proposal (informational only; not executed this turn)
rewind_target: continue_same
[Codex #10] Stage 3 code-edit review - IMP-33 #61 (R6 u6)
unit_verified: u6
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u7, u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #11] Stage 3 code-edit — IMP-33 #61 (R7 u7)
Executing unit: u7
■ unit_executed
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 validatedAiFallbackProposal. Save to cache is NOT performed here; it is caller-driven AFTERvisual_check_passed=TrueANDuser_approved=Trueper the u6 IMP-46 gate. Structural guarantee: the router does NOT importsave_proposal.■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/router.py:src/phase_z2_ai_fallback/, Stage 1 binding).route_ai_fallback(*, cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text, client=None) -> AiFallbackProposal | None.if not settings.ai_fallback_enabled: return None. Short-circuit BEFORE prompt/client work. DefaultFalse(u1) preserves PZ-1 normal-path AI=0.v4_result.route(or aliasimp05_route_hint). If notai_adaptation_required(=V4_ROUTE_AI_ADAPTATIONfrom u3), returnsNonewithout touching the prompt builder or client. This is the runtime-layer enforcement of PZ-1; u3 raisesValueErrorif reached anyway as defence-in-depth.read_proposal(cache_key)from u6. Cache hit returns the cached proposal AFTER re-validating it against the currentframe_contract+internal_region(defence-in-depth — frame contract may have changed since the proposal was cached).build_ai_fallback_prompt(...), instantiatesAiFallbackClient()if not injected, callsrequest_proposal(prompt), validates via u5validate_proposal(...), and returns.save_proposalimport — caller is responsible for writing to cache only after visual_check + user_approved gates (per u6 contract).clientkwarg) is for test seams; production callers can omit it and the router constructs the u4 client viasettings(no inline policy literals; PZ-1 +feedback_no_hardcoding).tests/phase_z2_ai_fallback/test_router.py— 9 focused tests covering every axis Stage 2 listed for u7:test_router_returns_none_when_flag_off—ai_fallback_enabled=False, asserts resultis NoneANDclient.request_proposal.assert_not_called().test_router_returns_none_when_route_not_ai_adaptation— flag ON, V4 route =light_edit; assertsNoneAND client untouched.test_router_returns_cached_when_cache_hit— monkeypatchesrouter_mod.read_proposalto return a cached proposal; asserts resultis cachedAND client untouched.test_router_validates_cached_proposal— cache returns a proposal whosepayloadviolates u5 builder whitelist; router raisesAiFallbackValidationErrorAND does NOT call the client (cache-hit still validated against current contract).test_router_calls_client_and_returns_validated_proposal— happy path; asserts result identity, single client call, and prompt shape{system, user}.test_router_propagates_validation_error— client returns an invalid proposal (unknown builder key); router raisesAiFallbackValidationError.test_router_propagates_budget_exceeded— client side_effect =AiFallbackBudgetExceeded; propagates without retry (u4 owns retry, router does not catch).test_router_propagates_circuit_open— same axis forAiFallbackCircuitOpen.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.■ verification (this unit only, scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_router.py -q→ 9 passed in 0.23s.python -m pytest tests/phase_z2_ai_fallback/ -q→ 56 passed in 0.12s (47 prior across u1–u6 + 9 new for u7; no regression).rg "Anthropic|messages\.create|save_proposal" src/phase_z2_ai_fallback/router.py→ 1 hit (line 15 docstring, the negative guardrail sentence "does not importsave_proposal"). No code-level Anthropic import; nomessages.createcall; nosave_proposalimport. All AI traffic flows through u4 client via dependency.src/phase_z2_pipeline*.pyor any other pipeline / Phase Q / Kei module → normal-path AI call count unchanged.■ guardrail_checks
feedback_ai_isolation_contract: preserved — router does NOT read or write MDX; mdx_text flows through u3 prompt (READ-ONLY, system-prompt-enforced) only.feedback_phase_z_spacing_direction: preserved — router does NOT mutate V4 result; rank-1 frame_id propagates unchanged throughframe_contractto u5 frame-swap guard.feedback_artifact_status_naming: preserved — router does NOT save to cache; structurally cannot (save_proposalnot imported). Save is gated on visual_check + user_approved at the caller (u6 IMP-46 gate).V4_ROUTE_AI_ADAPTATIONsourced from u3; all client policy sourced from u1 via u4).■ remaining_units
■ follow_up_issue_candidates
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Codex #11] Stage 3 code-edit review - IMP-33 #61 (R7 u7)
unit_verified: u7
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u8, u9, u10, u11]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #12] Stage 3 code-edit — IMP-33 #61 (R8 u8)
Executing unit: u8
■ unit_executed
src/phase_z2_ai_fallback/step12.py). New module-level entrygather_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 withprovisional=Falseare skipped before any route classification (per Stage 2 "Step 12 IMP-30 provisional only"); (2) reject gate — units whose V4 label maps todesign_reference_onlyare skipped withskip_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_requiredunits, the helper assembles the u7 router inputs (cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text) and invokesroute_ai_fallback(...)once per unit. Save is NOT performed here — caller-driven AFTER visual_check + user_approved (u6 IMP-46 gate).■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/step12.py:src/phase_z2_ai_fallback/, Stage 1 binding)._AI_ADAPTATION_ROUTE = "ai_adaptation_required"and_DESIGN_REFERENCE_ROUTE = "design_reference_only"(mirrorssrc/phase_z2_pipeline.py:572-576_IMP05_ROUTE_HINTS).route_ai_fallback(u7). NO Anthropic import, NOmessages.create()call, NOsave_proposalimport (caller-only per u6 contract), NO MDX I/O (caller supplies loaded artifacts).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]:{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.if not unit.provisional: skip_reason="not_provisional"; continue. IMP-30 first-render survivors are the only path AI repair can touch.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).if route_hint != _AI_ADAPTATION_ROUTE: skip_reason=f"route_not_ai_adaptation:{route_hint}"; continue. Defence-in-depth against future route_hints.cache_key = f"{template_id}::{','.join(sorted(source_section_ids))}", assemblev4_result = {route, label, frame_id, rank, cardinality}, resolveframe_contractviaget_contract_fn,frame_visual_htmlviaframe_visual_loader, optionalfigma_partial_json/internal_region/mdx_textvia injected loaders (raw_content fallback). Callroute_ai_fallback(...)inside try/except. RuntimeError / API errors → recordai_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().save_proposal(which the helper does not import) can persist proposals, and only AFTER visual_check + user_approved gates.tests/phase_z2_ai_fallback/test_step12.py— 9 focused tests covering every Stage 2 axis u8 owns:test_non_provisional_unit_is_skipped_without_ai_call— IMP-30 provisional gate;router.assert_not_called().test_reject_route_is_skipped_without_ai_call— label="reject" (route_hint=design_reference_only) NEVER calls router;router.assert_not_called().test_non_ai_route_is_skipped_with_reason— label="light_edit" (deterministic_minor_adjustment) records skip_reason and does NOT call router.test_router_short_circuit_returns_none_skip_reason— router returns None (flag-off / route-mismatch surface) →ai_called=False,skip_reason="router_short_circuit".test_ai_adaptation_call_records_proposal— label="restructure" + provisional=True →ai_called=True, proposal serialized viamodel_dump(), router called withv4_result.route == "ai_adaptation_required".test_router_exception_is_captured_per_record—RuntimeError("transient_boom")→ai_called=True,error="RuntimeError: transient_boom", no re-raise.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.test_cache_key_includes_template_and_section_ids— cache_key contract ="{template_id}::{sorted_section_ids_csv}".test_record_shape_contract_is_stable— explicitset(rec.keys())assertion locks the per-record schema (downstream Step 12 artifact consumers).■ scope guardrails preserved (this unit)
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 u1ai_fallback_enabled=Falsedefault, even when the helper is invoked the u7 router short-circuits before any Anthropic call.feedback_ai_isolation_contract— u8 NEVER mutates MDX, NEVER reads MDX from disk (caller supplies viamdx_text_loaderor unit.raw_content), NEVER bypasses u6 cache gate (nosave_proposalimport).feedback_phase_z_spacing_direction— V4 rank-1 protected via u5 validator (called inside u7 router). u8 forwards the contract; never mutates frame_id.feedback_no_hardcoding— no sample-specific branches; route map injected viaroute_for_labelcallable (test parity with_IMP05_ROUTE_HINTS).feedback_one_step_per_turn— single unit (u8) executed; no follow-up unit started, no commit, no push.src/phase_z2_ai_fallback/(IMP-31-GATE-AUDIT Stage 1 binding).■ tests scope-qualified
tests/phase_z2_ai_fallback/test_step12.py: 9 passed in 0.13s.tests/phase_z2_ai_fallback/+tests/test_phase_z2_ai_fallback_config.pycombined: 69 passed in 0.21s (u1 + u2 + u3 + u4 + u5 + u6 + u7 + u8 → 4 + 9 + 11 + 8 + 10 + 9 + 9 + 9 = 69 ✓).■ remaining_units
■ follow_up_issue_candidates
ai_repair_proposalsfield. 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.figma_partial_loaderdefault 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.internal_region_lookupdefault of{}— once Layer A planning activates the render path (per current MEMORY noteproject_design_agent_status), this lookup will be injected by the caller.rewind_target: continue_same
FINAL_CONSENSUS: NO
[Codex #12] Stage 3 code-edit review - IMP-33 #61 (R8 u8)
unit_verified: u8
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u9, u10, u11]
Verified facts:
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #13] Stage 3 code-edit — IMP-33 #61 (R9 u9)
Executing unit: u9
■ unit_executed
src/phase_z2_ai_fallback/step17.py). Two responsibilities per Stage 2 plan — (1) SPECIFY the canonical overflow cascade orderdeterministic → popup → ai_repair → user_overrideviaOverflowCascadeStageenum +OVERFLOW_CASCADE_ORDERtuple (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 pointgather_step17_ai_repair_proposalsrecords every unit withai_called=False+skip_reason="step17_ai_blocked_imp_34_35_prerequisites_missing"+proposal=None, regardless of provisional / label / route_hint. The module does NOT importroute_ai_fallback(u7), does NOT import the u4 client, and does NOT importanthropic— block is enforced at the import boundary, not at runtime branches.■ files_changed
■ diff_summary
src/phase_z2_ai_fallback/step17.py:src/phase_z2_ai_fallback/, Stage 1 binding).enum.Enum,typing.Any/Callable/Iterable,__future__.annotations). NOanthropicimport. NOsrc.phase_z2_ai_fallback.routerimport. NOsrc.phase_z2_ai_fallback.clientimport. NOmessages.create()call. NO MDX I/O. NOsave_proposalreference. The u9 layer is structurally inert with respect to AI.OverflowCascadeStage(str, Enum)— 4 canonical stages with docstring mapping to source IMPs: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).POPUP = "popup"— IMP-35 (details_popup_escalation, pending). Final deterministic resort before any AI.AI_REPAIR = "ai_repair"— IMP-33 (this carve-out) + IMP-46 cache. Reachable ONLY after DETERMINISTIC + POPUP exhausted AND user-approved fallback budget.USER_OVERRIDE = "user_override"— explicit user override after all auto stages.OVERFLOW_CASCADE_ORDER: tuple[OverflowCascadeStage, ...]— pinned(DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE). Aligns withdocs/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'sguardrail / validationline ("overflow chain 순서 준수: zone resize → responsive fit → popup → AI+cache → 사용자 명시 override").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'snot_provisional/design_reference_only_no_ai/route_not_ai_adaptation:*/router_short_circuitskip paths.gather_step17_ai_repair_proposals(units, *, route_for_label) -> list[dict]:cascade_stagepins 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).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.provisional/label/route_hint. No branching, no early-out — uniform block at the u9 boundary.tests/phase_z2_ai_fallback/test_step17.py— 14 focused tests covering every axis Stage 2 listed for u9 plus the structural import guarantees:test_overflow_cascade_order_is_canonical—OVERFLOW_CASCADE_ORDERequals the 4-stage canonical tuple in exact order.test_overflow_cascade_stage_string_values— each enum member's.valuematches the issue body's cascade vocabulary.test_step17_blocked_reason_constant_value— pin the sentinel string so downstream artifact consumers cannot accidentally drift.test_gather_returns_one_record_per_unit— 3 units → 3 records.test_gather_records_blocked_skip_reason— every record carriesSTEP17_AI_REPAIR_BLOCKED_REASON.test_gather_blocks_even_when_route_is_ai_adaptation_required— provisional +restructure(routeai_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.test_gather_blocks_reject_units_too—rejectunits (routedesign_reference_only) get the same block reason (defence-in-depth, not the u8design_reference_only_no_aireason).test_gather_records_proposal_none_and_no_error— proposal/error fields are null, never populated.test_gather_records_cascade_stage_is_ai_repair—cascade_stageis always"ai_repair".test_gather_preserves_unit_metadata— unit_index / frame_template_id / source_section_ids / label / provisional all propagate to the record.test_gather_with_empty_units_returns_empty_list— empty input → empty output (no implicit unit injection).step17.py):test_step17_module_does_not_import_route_ai_fallback— neithersrc.phase_z2_ai_fallback.routernorroute_ai_fallbackis in u9's import graph;hasattr(step17_mod, "route_ai_fallback")is False.test_step17_module_does_not_import_anthropic— noanthropic.*import anywhere in u9.test_step17_module_does_not_import_ai_fallback_client— nosrc.phase_z2_ai_fallback.clientimport (soAiFallbackClientis also unreachable). u10 will extend this kind of guard package-wide; u9 pins the boundary for this single module today.■ guardrails preserved
feedback_ai_isolation_contract: u9 enforces the carve-out by structurally not importingroute_ai_fallbackoranthropic. AI cannot leak from u9 even if a future caller mistakes it for an active path.feedback_phase_z_spacing_direction: no spacing shrink, no global margin reduction. u9 only records cascade-stage metadata.feedback_no_hardcoding(RULE 7): no MDX-specific case, no sample-specific branch. Cascade order constants come fromdocs/architecture/IMP-17-CARVE-OUT.md:16+ issue body guardrail line — not hardcoded to any one MDX.feedback_one_step_per_turn(PZ-2): exactly one unit (u9) executed this turn. u10 (AST isolation guard) and u11 (docs sync) NOT touched.src/phase_z2_ai_fallback/per IMP-31-GATE-AUDIT Stage 1 binding.■ test results (scope-qualified)
python -m pytest tests/phase_z2_ai_fallback/test_step17.py -q— 14/14 PASS, 0.03s.python -m pytest tests/phase_z2_ai_fallback -q— 79/79 PASS, 0.09s (no regression in u1~u8 tests).■ remaining_units
phase_z2_pipeline/ Phase Q / Kei runtime imports fromsrc/phase_z2_ai_fallback/*; package-wide structural guard that complements u9's per-module guard).■ follow_up_issue_candidates
route_ai_fallbackunder the DETERMINISTIC+POPUP+budget conjunction.pair: IMP-46); u9 does not introduce any new cache concern.[Codex #13] Stage 3 code-edit review - IMP-33 #61 (R9 u9)
unit_verified: u9
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u10, u11]
Verified facts:
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #14] Stage 3 code-edit — IMP-33 #61 (R10 u10)
Executing unit: u10
■ unit_executed
tests/phase_z2_ai_fallback/test_ast_isolation.py). Structural defence that parses every*.pyfile undersrc/phase_z2_ai_fallback/and asserts none of them imports a Phase Q runtime module, the Kei API client, or anyphase_z2_*runtime module. Whitelist-based primary check + explicit blacklist parameterised check + asrc.phase_z2_pipelinesentinel + asrc.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.■ files_changed
■ diff_summary
tests/phase_z2_ai_fallback/test_ast_isolation.py:pathlib.Path(__file__).resolve().parents[2] / "src" / "phase_z2_ai_fallback"— no relativeos.chdirassumption, no test ordering dependency._module_files()returns sorted*.pyfiles undersrc/phase_z2_ai_fallback/(excludes__pycache__)._imported_names(tree)walks the AST forast.Import(alias names) andast.ImportFrom(module names). Collects every name reachable from the fallback package._ALLOWED_SRC_PREFIXES = ("src.config", "src.phase_z2_ai_fallback")— the only twosrc.*prefixes the fallback package may import.src.configis the single source of truth for u1 policy knobs (no inline literals —feedback_no_hardcoding); intra-packagesrc.phase_z2_ai_fallback.*covers schema (u2), prompts (u3), client (u4), validate (u5), cache (u6), router (u7), step12 (u8), step17 (u9)._ALLOWED_TOP_LEVEL= stdlib (__future__,ast,dataclasses,enum,json,pathlib,random,time,typing) +anthropic(u4 SDK) +pydantic(u2 schema). Anything else fails the whitelist._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".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.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.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).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.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.■ test results (scope-qualified)
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).python -m pytest tests/phase_z2_ai_fallback/ -q→ 101 passed in 0.17s (no regression in u1~u9 prior suites).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).■ verified facts
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.normal_path_ai_zero), but it is NOT sample-specific and NOT MDX-specific — it is the runtime module taxonomy that already exists insrc/. Adding a new Phase Q module tosrc/will not silently bypass the check because the whitelist (test 2) defaults to "deny" — any newsrc.*import outside the two allowed prefixes will fail test 2 regardless of whether the blacklist (test 3) has been updated.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).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, orphase_z2_*runtime sibling imports are present.■ guardrails preserved
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 canimport src.phase_z2_pipelineand call into a path that rewrites MDX.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.feedback_one_step_per_turn: this turn executes only u10. Remaining unit u11 is NOT being touched in this turn.src/phase_z2_ai_fallback/is the lockedfolder.■ remaining_units
■ follow_up_issue_candidates
_IMP05_ROUTE_HINTSinsrc/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 fromsrc.phase_z2_pipeline. Today the fallback package side-steps this by hardcoding the route constants (u3V4_ROUTE_AI_ADAPTATION, u8_AI_ADAPTATION_ROUTE/_DESIGN_REFERENCE_ROUTE, u9OverflowCascadeStage). This is acceptable for now — u11 (docs sync) is the appropriate place to record the convention.[Codex #14] Stage 3 code-edit review - IMP-33 #61 (R10 u10)
unit_verified: u10
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u11]
Verified facts:
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #15] Stage 3 code-edit — IMP-33 #61 (R11 u11)
Executing unit: u11
■ unit_executed
docs/architecture/IMP-17-CARVE-OUT.mdanddocs/architecture/IMP-31-GATE-AUDIT.mdso the design-only language is replaced with binding references to the IMP-33 u1~u10 runtime module surface. Addstests/phase_z2_ai_fallback/test_docs_sync.pyas a structural pin so future drift between code and docs fails the test suite (not silently rotted prose).■ files_changed
■ diff_summary
docs/architecture/IMP-17-CARVE-OUT.md:ai_fallback_enabledflag defaultFalseinsrc/config.py(u1), Step 12 entry provisional-gated (u8), Step 17 entry structurally blocked behind IMP-34 + IMP-35 (u9).src/phase_z2_ai_fallback/, cross-linked toIMP-31-GATE-AUDIT.md:31,50,56 (Stage 1 binding lock).src.phase_z2_ai_fallback.step12.gather_step12_ai_repair_proposals; records the three structural gates (not_provisionalskip,design_reference_only_no_aiskip, non-AI route catch-all) that run BEFOREroute_ai_fallback. Mirrors the u8 contract exactly.src.phase_z2_ai_fallback.step17.gather_step17_ai_repair_proposals; explicit STRUCTURALLY BLOCKED label with thestep17_ai_blocked_imp_34_35_prerequisites_missingsentinel + the import-graph guarantee (does NOT importroute_ai_fallback/AiFallbackClient/anthropic).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.save_proposal(..., visual_check_passed, user_approved)raisesAiFallbackCacheGateErrorunless BOTH gates are True; storage backend then raisesNotImplementedError(the IMP-46 marker).read_proposalreturnsNoneuntil IMP-46 lands a backend.tests/phase_z2_ai_fallback/test_ast_isolation.pywhitelist (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.docs/architecture/IMP-31-GATE-AUDIT.md: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, butsettings.ai_fallback_enableddefaults toFalse(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.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 toIMP-17-CARVE-OUT.md§"Runtime module surface" so the audit reader can find the binding surface in one hop.tests/phase_z2_ai_fallback/test_docs_sync.py(NEW):test_carve_out_doc_references_runtime_surface) over 10 needles covering every axis Stage 2 listed for u11:src/phase_z2_ai_fallback/(module path)gather_step12_ai_repair_proposals(Step 12 entry, u8)gather_step17_ai_repair_proposals(Step 17 entry, u9)step17_ai_blocked_imp_34_35_prerequisites_missing(Step 17 blocked sentinel, u9)OVERFLOW_CASCADE_ORDER(cascade single source of truth, u9)(DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE)(cascade order content, u9)visual_check_passed,user_approved(IMP-46 gate flags, u6)AiFallbackCacheGateError(IMP-46 gate exception, u6)ai_fallback_enabled(PZ-1 flag default OFF, u1)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) theai_fallback_enabledflag (so future readers can see PZ-1 is preserved at the audit layer too).Path(__file__).resolve().parents[2] / "docs" / "architecture"— repo-root anchored, nocwdassumption, no test ordering dependency.■ guardrails_verified
settings.ai_fallback_enableddefault =False(u1) — re-stated in the binding table so PZ-1 is documented at the carve-out layer, not just insidesrc/config.py.feedback_anchor_sync_rules: cross-references betweenIMP-17-CARVE-OUT.mdandIMP-31-GATE-AUDIT.mdare 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".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.feedback_scope_qualified_verification: the docs-sync test runs overdocs/architecture/IMP-17-CARVE-OUT.mdanddocs/architecture/IMP-31-GATE-AUDIT.mdonly. 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.src/phase_z2_ai_fallback/(not atsrc/phase_z2_ai_repair.pyor any path implied by the issue body text).■ tests_run
python -m pytest tests/phase_z2_ai_fallback/test_docs_sync.py -q■ remaining_units
■ follow_up_issue_candidates
save_proposal(currently raisesNotImplementedErrorafter the gate). Out-of-scope here per Stage 2 plan.gather_step12_ai_repair_proposalsas 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.rewind_target: continue_same
FINAL_CONSENSUS: NO (Stage 3 final-pass consensus = u11 unit-level review by Codex; YES is gated on Codex verification)
[Codex #15] Stage 3 code-edit review - IMP-33 #61 (R11 u11)
unit_verified: u11
result: pass
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: none
Verified facts:
FINAL_CONSENSUS: YES
[Claude #16] Stage 4 test-verify — IMP-33 #61 (R1)
📌 Stage 4 verdict: PASS
■ Verification axes (Stage 4 contract)
■ Test runs (commands + counts)
python -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=short→ 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).
python -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -q→ 161 passed in 28.38s (Phase Z2 regression sweep — no normal-path failure).
python -m pytest -q --ignore=tests/matching --ignore=scripts→ 526 passed in 42.67s (full sweep minus tests/matching ad-hoc analysis dir and scripts/ collection-only directory).
■ Pre-existing failures NOT introduced by IMP-33
scripts/test_phase_t_audit.py,scripts/test_phase_t_full.py,scripts/test_phase_t_real.pyraiseFileNotFoundError: data\runs\20260401_151426\stage_0_context.jsonduring 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 inscripts/. Scope: pre-existing, not regression.■ Diff vs Stage 2 plan (u1~u11)
src/config.py:14-26adds 9ai_fallback_*fields: master flag defaultFalse, modelclaude-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.src/phase_z2_ai_fallback/schema.py:22-30definesProposalKindwhitelist +FORBIDDEN_KINDS = {"mdx_text","frame_id_change","raw_html","raw_css"};AiFallbackProposalusesextra="forbid"(test_schema rejects mdx_text/frame_id_change/raw_html/raw_css/unknown/extra → 7 cases pass).src/phase_z2_ai_fallback/prompts.py:23-35SYSTEM_PROMPTenforces MDX READ-ONLY + whitelist + forbidden + frame_id swap lock + slot population + Internal Region containment;:55-61raises onroute != 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.src/phase_z2_ai_fallback/client.py:43-92AiFallbackClientsources every knob fromsettings.ai_fallback_*(no inline literals); transient retry set =(APITimeoutError, APIConnectionError, RateLimitError, InternalServerError); budget + circuit accounting per-instance.src/phase_z2_ai_fallback/validate.py:31-83four guards: schema revalidation, builder whitelist (only keys inframe_contract.payload.builder_options), dropped-slot guard (every declaredsub_zones[*].idmust remain), frame-swap guard (payload.frame_idmust equal contractframe_id), Internal Region containment.src/phase_z2_ai_fallback/cache.py:36-82read_proposalreturnsNone;save_proposal(..., visual_check_passed, user_approved)raisesAiFallbackCacheGateErrorunless BOTH True; once both True, storage raisesNotImplementedError("...IMP-33 u6 stub marker.")— IMP-46 marker.src/phase_z2_ai_fallback/router.py:43-89flag-off short-circuit BEFORE prompt/client; route-mismatch short-circuit; cache read; build prompt; client call; validate. Does NOT importsave_proposal(test_router enforces).src/phase_z2_ai_fallback/step12.py:32-141gather_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 BEFOREroute_ai_fallback. Record schema stable across all gate paths.src/phase_z2_ai_fallback/step17.py:40-111OVERFLOW_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_proposalsrecords BLOCKED for every unit; module does NOT importroute_ai_fallback,anthropic, orAiFallbackClient(test_step17 enforces 3 import-absence assertions).tests/phase_z2_ai_fallback/test_ast_isolation.py:71-153parses every*.pyundersrc/phase_z2_ai_fallback/; whitelist =src.config+ intra-package + stdlib +anthropic+pydantic; rejectssrc.pipeline,src.kei_client,src.phase_z2_pipeline, and any othersrc.phase_z2_*runtime.tests/phase_z2_ai_fallback/test_docs_sync.py:25-61parametrises 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) acrossIMP-17-CARVE-OUT.md+ standaloneIMP-31-GATE-AUDIT.mdcheck for "scaffolded under IMP-33" + flag mention.■ Hardcoding + scope-creep audit
(03[._-]|04[._-]|05[._-]|mdx[/_]\d|sample_\d|BIM|건설산업)oversrc/phase_z2_ai_fallback/**→ onlyimp05_route_hint(V4 fallback rank alias from prompts.py:55 / router.py:63;05is the IMP-05 axis identifier, not sample 05.mdx — confirmed by surrounding token). No sample-specific case branches.(60\.0|3\b|8\.0|0\.3|10\b|5\b).*(timeout|retry|backoff|budget|circuit)oversrc/phase_z2_ai_fallback/**→ only step17.py:30 docstring mention of "user-approved fallback budget" (prose, not a literal). No inline policy literals.(time\.sleep|requests\.post|httpx|urllib|claude-opus|anthropic\.Anthropic\()oversrc/phase_z2_ai_fallback/**→ 2 hits:anthropic.Anthropic(api_key=settings.anthropic_api_key, timeout=settings.ai_fallback_timeout_s)at client.py:53 andtime.sleep(delay)at client.py:89 wheredelayis computed fromsettings.ai_fallback_backoff_*. No hardcoded delays.(write_text|\.write\(|open\(.+["']w)oversrc/phase_z2_ai_fallback/**→ 0 hits. No file writes from the fallback package.(mdx_text|mdx_content|mdx_raw).*=\s*[^READ]oversrc/phase_z2_ai_fallback/**→ router.py:80 forwardsmdx_text=mdx_text, step12.py reads via caller-suppliedmdx_text_loader. No MDX writes.src/phase_z2_ai_fallback/*.pylooking forast.Compareconstants containing'03' | '04' | '05' | 'sample' | 'mdx_batch'→ 0 violations.■ Regression check (Phase Z normal-path AI=0)
settings.ai_fallback_enableddefault =False(u1 lock). Router u7 short-circuits BEFORE any prompt/client work, soanthropic.Anthropicis never instantiated on the default normal path.src.config+ intra-package + stdlib +anthropic+pydanticonly — cannot reachsrc.phase_z2_pipelineor any other phase_z2_* runtime.route_ai_fallbackimport in step17.py). Confirmed by 3 import-absence tests.=== EVIDENCE ===
python -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=short→ 116 passed in 0.22s.python -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -q→ 161 passed in 28.38s.python -m pytest -q --ignore=tests/matching --ignore=scripts→ 526 passed in 42.67s.git diff --stat HEAD):docs/architecture/IMP-17-CARVE-OUT.md+12/-1docs/architecture/IMP-31-GATE-AUDIT.md+4/-4src/config.py+12/-0src/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.scripts/test_phase_t_*.py, none in IMP-33 surface.FINAL_CONSENSUS: YES
[Codex #16] Stage 4 test-verify - IMP-33 #61
Stage 4 verdict: PASS
Verification result:
tests/matchingandscriptsignored, matching the Stage 4 verification scope.Diff and contract checks:
src/config.pydefinesai_fallback_enabled=False, modelclaude-opus-4-6-20250415, timeout60.0, retries3, backoff base/cap/jitter1.0/8.0/0.3, budget10, circuit threshold5.src/phase_z2_ai_fallback/schema.pywhitelistsbuilder_options_patch,partial_overrides,slot_mapping_proposal; rejectsmdx_text,frame_id_change,raw_html,raw_css; usesextra="forbid".src/phase_z2_ai_fallback/prompts.pybuilds only forai_adaptation_required, carries V4/cardinality, frame contract, frame visual HTML, figma partial JSON, Internal Region, andmdx_text_READ_ONLY.src/phase_z2_ai_fallback/client.pywrapsanthropic.Anthropic(...).messages.create, sources retry/timeout/backoff/budget/circuit knobs from settings, and retries only timeout/connection/429/5xx classes.src/phase_z2_ai_fallback/validate.pyenforces builder-option whitelist, dropped-slot guard, frame-swap guard, and Internal Region containment.src/phase_z2_ai_fallback/cache.pyreturns cache miss until IMP-46 and raisesAiFallbackCacheGateErrorunless bothvisual_check_passedanduser_approvedare true.src/phase_z2_ai_fallback/router.pyshort-circuits on flag-off and non-AI route before prompt/client work; it reads cache, builds prompt, calls client, validates, and does not importsave_proposal.src/phase_z2_ai_fallback/step12.pygates Step 12 AI proposals behind provisional units, reject exclusion, andai_adaptation_requiredroute before calling the router.src/phase_z2_ai_fallback/step17.pykeeps Step 17 structurally blocked behind IMP-34/IMP-35, definesOVERFLOW_CASCADE_ORDER, and does not import router/client/Anthropic.tests/phase_z2_ai_fallback/test_ast_isolation.pypins fallback package import isolation.docs/architecture/IMP-17-CARVE-OUT.md,docs/architecture/IMP-31-GATE-AUDIT.md, andtests/phase_z2_ai_fallback/test_docs_sync.pypin runtime surface and doc/code sync.Scope notes:
python -m pytest scripts/test_phase_t_audit.py scripts/test_phase_t_full.py scripts/test_phase_t_real.py -q --tb=shortstill fails during collection outside IMP-33. Verified failures aretemplates/catalog.yamlmissing forscripts/test_phase_t_audit.py, anddata/runs/20260401_151426/stage_0_context.jsonmissing forscripts/test_phase_t_full.pyandscripts/test_phase_t_real.py.=== EVIDENCE ===
Commands run:
git status --shortgit diff --name-onlyrg -n "ai_fallback|phase_z2_ai_fallback|gather_step12|step17|OVERFLOW_CASCADE_ORDER|AiFallbackCacheGateError" src docs tests -Sgit 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_fallbackrg -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 -Spython -m pytest tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py -v --tb=shortpython -m pytest tests/test_family_contract_baseline.py tests/phase_z2/ -qpython -m pytest -q --ignore=tests/matching --ignore=scriptspython -m pytest scripts/test_phase_t_audit.py scripts/test_phase_t_full.py scripts/test_phase_t_real.py -q --tb=shortgit diff -- templates/phase_z2/catalog/frame_contracts.yaml templates/phase_z2/families/_WIP_FILES.md tests/test_family_contract_baseline.pyFiles checked:
src/config.pysrc/phase_z2_ai_fallback/__init__.pysrc/phase_z2_ai_fallback/schema.pysrc/phase_z2_ai_fallback/prompts.pysrc/phase_z2_ai_fallback/client.pysrc/phase_z2_ai_fallback/validate.pysrc/phase_z2_ai_fallback/cache.pysrc/phase_z2_ai_fallback/router.pysrc/phase_z2_ai_fallback/step12.pysrc/phase_z2_ai_fallback/step17.pytests/test_phase_z2_ai_fallback_config.pytests/phase_z2_ai_fallback/test_schema.pytests/phase_z2_ai_fallback/test_prompts.pytests/phase_z2_ai_fallback/test_client_mock.pytests/phase_z2_ai_fallback/test_validate.pytests/phase_z2_ai_fallback/test_cache.pytests/phase_z2_ai_fallback/test_router.pytests/phase_z2_ai_fallback/test_step12.pytests/phase_z2_ai_fallback/test_step17.pytests/phase_z2_ai_fallback/test_ast_isolation.pytests/phase_z2_ai_fallback/test_docs_sync.pydocs/architecture/IMP-17-CARVE-OUT.mddocs/architecture/IMP-31-GATE-AUDIT.mdTest results:
tests/phase_z2_ai_fallback/ tests/test_phase_z2_ai_fallback_config.py: 116 passed in 0.19s.tests/test_family_contract_baseline.py tests/phase_z2/: 161 passed in 26.77s.tests/matchingandscripts: 526 passed in 42.09s.templates/catalog.yamlordata/runs/20260401_151426/stage_0_context.json.Verified facts:
settings.ai_fallback_enabled=False.FINAL_CONSENSUS: YES
[Claude #17] Stage 5 commit-push — IMP-33 #61
📌 Stage 5: 커밋 및 푸쉬 R1 완료
■ 핵심 결정
c864fe0으로 묶어 push 완료. origin (GitHub) + slide2 (Gitea mirror, 본 이슈 호스트) 양쪽 remote tip 이c864fe0로 동일.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.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등.--no-verifyX). gpg signing 무변경. amend 없음.ai_fallback_enabled기본 OFF — production import 시 normal-path AI 호출 수 = 0 유지.■ 범위 제외
■ 다음 단계
=== EXIT REPORT (English, binding contract) ===
commit_sha:
c864fe0479c1513afb4eb29c6b96342af5b50178commit_message_subject:
feat(#61): IMP-33 AI fallback scaffolding (u1~u11, flag default OFF)files_staged (24 files, +2119/-5):
src/config.py(M, +12/-0)src/phase_z2_ai_fallback/__init__.py(A, 15)src/phase_z2_ai_fallback/cache.py(A, 82)src/phase_z2_ai_fallback/client.py(A, 92)src/phase_z2_ai_fallback/prompts.py(A, 80)src/phase_z2_ai_fallback/router.py(A, 89)src/phase_z2_ai_fallback/schema.py(A, 50)src/phase_z2_ai_fallback/step12.py(A, 141)src/phase_z2_ai_fallback/step17.py(A, 111)src/phase_z2_ai_fallback/validate.py(A, 83)tests/phase_z2_ai_fallback/__init__.py(A, 0)tests/phase_z2_ai_fallback/test_ast_isolation.py(A, 153)tests/phase_z2_ai_fallback/test_cache.py(A, 90)tests/phase_z2_ai_fallback/test_client_mock.py(A, 151)tests/phase_z2_ai_fallback/test_docs_sync.py(A, 61)tests/phase_z2_ai_fallback/test_prompts.py(A, 100)tests/phase_z2_ai_fallback/test_router.py(A, 156)tests/phase_z2_ai_fallback/test_schema.py(A, 46)tests/phase_z2_ai_fallback/test_step12.py(A, 193)tests/phase_z2_ai_fallback/test_step17.py(A, 208)tests/phase_z2_ai_fallback/test_validate.py(A, 144)tests/test_phase_z2_ai_fallback_config.py(A, 46)docs/architecture/IMP-17-CARVE-OUT.md(M, +11/-2)docs/architecture/IMP-31-GATE-AUDIT.md(M, +5/-3)files_explicitly_excluded (Stage 4 guardrail):
templates/catalog/templates/phase_z2/catalog/frame_contracts.yaml(M, #42 territory)templates/phase_z2/families/_WIP_FILES.md(M)templates/phase_z2/families/app_sw_package_vs_solution.htmltemplates/phase_z2/families/pre_construction_model_info_stacked.htmltemplates/phase_z2/frames/tests/test_family_contract_baseline.py(M, baseline regression suite unrelated to #61)tests/matching/**(legacy matching workshop artifacts)tests/pipeline/,tests/PIPELINE.md,tests/PLAN.md,tests/PROGRESS.md,tests/README.mdscripts/post_gitea_comment.py,scripts/sync_phase_z_run_to_frontend.pysamples/**,chromedriver/**.claude/settings.local.json,.orchestrator/,tmpclosed_issues.json, miscellaneoustmpartifactspush_targets:
origin(git@github.com:keimin86/design_agent.git) →mainc412f1e..c864fe0slide2(https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2.git) →mainc412f1e..c864fe0gitearemote (URL_Geulbeot.git) NOT pushed (different project perreference_design_agent_remotes).remote_verification:
git rev-parse HEAD→c864fe0479c1513afb4eb29c6b96342af5b50178git ls-remote origin main→c864fe0479c1513afb4eb29c6b96342af5b50178git ls-remote slide2 main→c864fe0479c1513afb4eb29c6b96342af5b50178hooks: pre-commit / commit-msg hooks ran clean (no
--no-verify). No gpg sign override. No amend. Single new commit on top ofc412f1e.diff_scope_check:
git diff --staged --statbefore commit showed exactly the 24 paths above; no creep into excluded list.pass_decision: PASS
unresolved_questions:
AiFallbackCacheGateErroruntil visual_check_passed AND user_approved). Not blocking Stage 6.guardrails:
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 followsfeat(#NN): IMP-MM <subject>style ofc412f1e/1efbf67/b4872ba).reference_design_agent_remoteshonored — pushed toslide2, skippedgitea(_Geulbeot.git).evidence:
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)git diff --staged --stat(pre-commit) listed exactly 24 files matching Stage 4 allowlist.git push origin main→c412f1e..c864fe0 main -> maingit push slide2 main→remote: Processed 1 references in total/c412f1e..c864fe0 main -> maingit ls-remote origin main/git ls-remote slide2 mainboth returnc864fe0479c1513afb4eb29c6b96342af5b50178.source_comment_ids: Claude #16 (Stage 4 test-verify PASS, exit report binding), Codex #16 (Stage 4 verification).
[Codex #17] Stage 5 commit-push verification - IMP-33 #61
Stage 5 verification result: PASS.
Verified commit SHA on local HEAD, origin, and slide2:
HEAD=c864fe0479c1513afb4eb29c6b96342af5b50178origin/main=c864fe0479c1513afb4eb29c6b96342af5b50178slide2/main=c864fe0479c1513afb4eb29c6b96342af5b50178Unintended files in commit: NO.
The commit contains exactly 24 files and matches the Stage 5 scoped IMP-33 paths:
src/config.pysrc/phase_z2_ai_fallback/**tests/phase_z2_ai_fallback/**tests/test_phase_z2_ai_fallback_config.pydocs/architecture/IMP-17-CARVE-OUT.mddocs/architecture/IMP-31-GATE-AUDIT.mdPush success / remote reflects: YES. Live
git ls-remotechecks for both configured targets returned the exact Stage 5 commit SHA onrefs/heads/main.Residual 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/, andtmpclosed_issues.json.=== EVIDENCE ===
Commands run:
git rev-parse HEADgit status --shortgit show --stat --oneline --decorate --name-status --format=fuller c864fe0479c1513afb4eb29c6b96342af5b50178git remote -vgit ls-remote origin refs/heads/maingit ls-remote slide2 refs/heads/maingit show --shortstat --format= c864fe0479c1513afb4eb29c6b96342af5b50178git diff-tree --no-commit-id --name-only -r c864fe0479c1513afb4eb29c6b96342af5b50178git diff --cached --name-onlygit branch --show-currentgit log -1 --format="%H%n%s"Files checked:
git diff-treeforc864fe0479c1513afb4eb29c6b96342af5b50178git status --shortgit remote -vTest 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.
Commit SHA:
c864fe0479c1513afb4eb29c6b96342af5b50178Verified facts:
main.HEADequalsc864fe0479c1513afb4eb29c6b96342af5b50178.feat(#61): IMP-33 AI fallback scaffolding (u1~u11, flag default OFF).24 files changed, 2119 insertions(+), 5 deletions(-).origin/mainandslide2/mainboth resolve toc864fe0479c1513afb4eb29c6b96342af5b50178via livegit ls-remote.git diff --cached --name-onlyreturned no paths; nothing is staged after the commit.FINAL_CONSENSUS: YES
[Claude #18] Stage 6 final-close — IMP-33 #61
📌 Final close review (Round #1)
■ Issue body re-read — original scope vs locked scope
Anthropic().messages.create()실선 추가"; chain partners explicitly named as IMP-34 / IMP-35 / IMP-46.src/phase_z2_ai_fallback/with master flag default OFF, route-gating, and structural pins. Routes split as:overflow chain order: zone resize → responsive fit → popup → AI+cache → user override.feedback_ai_isolation_contract+ V4 spec).■ Commit verification on origin (and Gitea mirror)
c864fe0479c1513afb4eb29c6b96342af5b50178git ls-remote origin refs/heads/main→c864fe0479c1513afb4eb29c6b96342af5b50178git ls-remote slide2 refs/heads/main→c864fe0479c1513afb4eb29c6b96342af5b50178src/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).src/config.py:19confirmsai_fallback_enabled: bool = False→ normal-path AI call count = 0 on production import.■ Goal vs result (per issue body axes)
src/phase_z2_ai_fallback/Anthropic().messages.create()real wireclient.pywraps anthropic SDK with timeout/retry/backoff/budget/circuitprompts.pycarries V4/cardinality/frame_contract/frame_visual/figma_partial/Internal Region/MDX-READ-ONLYschema.pywhitelist +extra="forbid"claude-opus-4-6-20250415ai_fallback_model = "claude-opus-4-6-20250415"validate.pyframe-swap guardOVERFLOW_CASCADE_ORDERconstant + step17 gatedcache.pyraisesAiFallbackCacheGateError; storageNotImplementedErrorfor IMP-46■ Follow-up links (not blocking close)
cache.pyreturns miss until IMP-46; gate is enforced but persistence isNotImplementedError. Pair-issue per #61 body.data\runs\20260401_151426\stage_0_context.jsonfixture), predates IMP-33, no overlap. Not a regression; can be cleaned in a separate audit IMP.■ Labels — issue currently has no labels. Recommend no label change (repo convention does not require labels at close).
■ Review-loop checklist (issue body)
■ Close decision: CLOSE
Rationale:
c864fe0is the single decision unit; identical on local HEAD, origin/main, slide2/main.=== EXIT REPORT (English, binding contract) ===
[Codex #18] Stage 6 final-close verification - IMP-33 #61
Final independent verification result: PASS. Close OK.
Scope-qualified review:
c864fe0479c1513afb4eb29c6b96342af5b50178is local HEAD and is the tip of bothorigin/mainandslide2/main.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, anddocs/architecture/IMP-31-GATE-AUDIT.md.src/config.pykeepsai_fallback_enabled: bool = False, so production import does not enable normal-path AI calls.src/phase_z2_ai_fallback/client.pywrapsanthropic.Anthropic(...).messages.create(...)with model, timeout, retry, backoff, budget, and circuit settings sourced fromsrc.config.settings.ai_adaptation_required; router importsread_proposalbut does not importsave_proposal.builder_options_patch,partial_overrides, andslot_mapping_proposal; forbiddenmdx_text,frame_id_change,raw_html, andraw_cssare rejected withextra="forbid".mdx_text_READ_ONLY.AiFallbackCacheGateErrorunless bothvisual_check_passedanduser_approvedare true, then raisesNotImplementedErroruntil IMP-46 persistent storage lands.design_reference_only_no_ai.DETERMINISTIC, POPUP, AI_REPAIR, USER_OVERRIDE.c864fe0.No rewind target required.
=== EVIDENCE ===
Commands run:
git rev-parse HEADgit ls-remote origin refs/heads/maingit ls-remote slide2 refs/heads/maingit diff-tree --no-commit-id --name-only -r c864fe0479c1513afb4eb29c6b96342af5b50178git show --shortstat --oneline --no-renames c864fe0479c1513afb4eb29c6b96342af5b50178rg -n "ai_fallback_|claude-opus|timeout|retry|backoff|budget|circuit" src/config.pyrg -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.pyrg -n "Anthropic|messages\\.create|timeout|retry|429|500|529|Connection|RateLimit|Overloaded|budget|circuit" src/phase_z2_ai_fallback/client.pyrg -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.pyrg -n "dropped|frame|Internal|contain|builder|text|table|image|details|rank|slot" src/phase_z2_ai_fallback/validate.pyrg -n "read_proposal|save_proposal|visual_check_passed|user_approved|NotImplementedError|AiFallbackCacheGateError" src/phase_z2_ai_fallback/cache.pyrg -n "ai_fallback_enabled|route|cache|build_ai_fallback_prompt|validate|save_proposal|read_proposal|client" src/phase_z2_ai_fallback/router.pyrg -n "gather_step12_ai_repair_proposals|design_reference_only|provisional|IMP-30|ai_adaptation_required|reject" src/phase_z2_ai_fallback/step12.pyrg -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.pyrg -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.mdpython -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 -qgit status --shortFiles checked:
src/config.pysrc/phase_z2_ai_fallback/schema.pysrc/phase_z2_ai_fallback/prompts.pysrc/phase_z2_ai_fallback/client.pysrc/phase_z2_ai_fallback/validate.pysrc/phase_z2_ai_fallback/cache.pysrc/phase_z2_ai_fallback/router.pysrc/phase_z2_ai_fallback/step12.pysrc/phase_z2_ai_fallback/step17.pytests/phase_z2_ai_fallback/test_docs_sync.pydocs/architecture/IMP-17-CARVE-OUT.mddocs/architecture/IMP-31-GATE-AUDIT.mdVerified facts:
c864fe0479c1513afb4eb29c6b96342af5b50178.origin/main=c864fe0479c1513afb4eb29c6b96342af5b50178.slide2/main=c864fe0479c1513afb4eb29c6b96342af5b50178.ai_fallback_enableddefault isFalse; model isclaude-opus-4-6-20250415; timeout/retry/backoff/budget/circuit fields are present insrc/config.py.save_proposalimport.route_ai_fallback,AiFallbackClient, oranthropic.FINAL_CONSENSUS: YES