Adds sub-section schema fields (heading_number / v4_alias_keys /
sub_sections) to MdxSection with defaults so existing 4-positional
constructions remain valid. Introduces _resolve_v4_section_key helper
that resolves a V4 mdx_sections key in exact > alias > None order with
no parent/sibling promotion (axis 7 hybrid lock).
Rewires four runtime V4 lookup sites (lookup_v4_match,
lookup_v4_match_with_fallback, lookup_v4_all_judgments,
lookup_v4_candidates) to accept an optional alias_keys kwarg and go
through the resolver. U1 callers pass empty alias lists so behaviour
is byte-identical to the previous exact-match path; U2 will populate
aliases from MDX heading_number metadata.
Closure callers in run_phase_z2 build section_alias_by_id from
MdxSection.v4_alias_keys and forward into lookup_fn /
candidates_lookup_fn / lookup_v4_all_judgments (Step 7-A trace) and
into _select_template_for_overrides single-section selector.
Step 9 candidate report (post-decision diagnostic) is marked with an
inline English exemption comment per N-R6 — runtime selection goes
through _resolve_v4_section_key, the report path stays a direct
dict-shape lookup to avoid debug_zones schema plumbing.
derive_parent_id now recognises canonical ordinal ids
("03-1-sub-2" -> "03-1") first and keeps the legacy decimal fallback
("04-2.1" -> "04-2") for V4 alias compatibility.
Tests : 8 synthetic cases in tests/test_phase_z2_subsection_schema.py
covering derive_parent_id ordinal/decimal/none and the resolver
exact/alias/no-promote/miss cases. 30/30 PASS combined with the 14
override + 8 fallback baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #6
After `position_assignment_plan` is built, rebuild the `units` list to be
plan-aligned so downstream `zones_data` / `debug_zones` / mapper / render
all see the post-override sequence. This resolves the long-standing
trace-only gap and closes Codex Catch K naturally because the helper now
actually drives downstream materialization.
- run_phase_z2_mvp1: after `_build_position_assignment_plan`, rebuild
`units` ordered by `position_assignment_plan`. `cli_override` entries
synthesize a CompositionUnit (resolved template_id + concatenated
section raw_content + contract frame_id + selection_path="cli_override"
+ override audit in rationale). `auto` entries reuse the original
planner unit. empty/collision-skipped entries become None placeholders
so the downstream zone loop can emit an explicit empty zone record
without distorting layout allocation.
- zones_data / debug_zones loop: handle `unit is None` by appending an
explicit empty record with template_id="__empty__", content_weight=0,
min_height_px=0, plus the plan's skipped_reason / replaced_auto_unit /
skipped_collided_auto_units / uncovered_section_ids audit fields.
- partial render loop: `template_id == "__empty__"` short-circuits to
`partial_html = ""` so the slide_base zones loop preserves grid identity
without raising TemplateNotFound.
- Update the helper-invocation comment so it now describes the actual
Part 2 behavior (units rebuild + empty zone handling). Catch K is no
longer a future-tense placeholder.
Stage 4 Part 3 (follow-up commit) will add: Step 9 application_plan
plan-aware additive fields, Step 20 list-shaped filtered_section_reasons
entries for override-uncovered sections (Codex #10 Catch O schema), and
integration tests proving zones_data["top"] actually contains the
overridden section ids when --override-section-assignment is supplied.
Regression: 20/20 unit tests pass (9 IMP-06 helper + 8 IMP-05 fallback +
2 catalog invariant + 1 dedicated replaced_auto_unit test), smoke
self-check 11/11 (IMP-04 F17 calibration intact).
No AI, no calculate_fit, no full planner rerun, no frontend, no sample
hardcoding. plan_composition() signature preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #6
Stage 4 split per Codex #10 acceptance: this commit lands the schema +
trace refinements required before the render-path rewiring. The actual
units/zones_data/Step 9/Step 20 plan-driven materialization remains in
Stage 4 part 2 (follow-up commit) so each commit is reviewable on its
own and regression-safe.
- _build_position_assignment_plan: add replaced_auto_unit field. Populated
only when the explicitly overridden position already held an auto unit
AND that auto unit had different source_section_ids than the override.
Documents a same-position override replacement as a distinct audit fact,
separate from skipped_collided_auto_units which captures cross-position
whole-skips per the locked collision policy.
- Backfill replaced_auto_unit = None on the empty/collision/auto branches
for schema-stable consumers.
- Update the override-application comment near the helper invocation so it
no longer claims the helper "reorders units"; Stage 4 part 2 will be the
commit that wires the plan into the actual render path.
- Helper unit tests: assert replaced_auto_unit shape in the collision
scenario and add a dedicated case that distinguishes same-sections
(template swap via --override-frame -> None) from different-sections
same-position replacement (populated, reason="same_position_override_replacement").
No AI, no calculate_fit, no full planner rerun, no frontend, no sample
hardcoding. plan_composition() signature preserved. helper remains pure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #6
Backend / CLI / composition path only — frontend bridge remains #38.
- Add `--override-section-assignment ZONE_ID=section_id[,section_id]` to the
Phase Z entry parser. Parse-time hard errors for malformed payloads, empty
zone id, empty section list, duplicate zone id, and duplicate section across
zones (a section may belong to at most one zone).
- Add `_build_position_assignment_plan` helper (pure function, resolved
`positions` injected). Builds a per-position assignment plan with the
Codex-locked template_id ladder: (1) `--override-frame` exact unit_id wins,
(2) exact existing auto unit reuse, (3) single-section direct-executable V4
selector via `lookup_v4_match_with_fallback(..., raw_content=section.raw_content)`,
(4) ad-hoc multi-section override without exact auto + without explicit
override-frame yields `skipped_reason='ad_hoc_merged_no_template'`.
- Lock the collision policy: explicit override wins per position, sections
appear in at most one position, overlapping auto units are skipped whole
(no split, no cascade, no replan), uncovered sections from the previous
same-position auto unit are recorded in `uncovered_section_ids`.
- Additive trace fields on each plan entry: `previous_source_section_ids`,
`skipped_collided_auto_units`, `uncovered_section_ids`, `v4_selector_trace`,
`section_assignment_override`. Top-level `comp_debug["section_assignment_plan"]`
+ `comp_debug["section_assignment_summary"]` so Step 9 / debug artifacts can
derive from a single source of truth.
- Wire `run_phase_z2_mvp1(override_section_assignments=...)` after final layout
preset resolution: validate ZONE_IDs against active layout positions and
validate section_ids against aligned sections (fail-fast). The plan is
attached to `comp_debug` for downstream artifacts. Actual `zones_data` /
unit-list rewiring is deferred to a follow-up commit so this change stays
regression-safe; trace artifacts already surface override intent and
collision impact.
- Add 9 helper unit tests with fully synthetic MOCK_ ids (no real catalog
/ no v4_full32_result.yaml): non-conflicting auto retention, collision
whole-skip + uncovered tracing, template ladder steps 1/2/4, unit_id
naming convention, previous_source_section_ids position history,
empty-position case, summary aggregation invariants.
No AI, no `calculate_fit`, no full planner rerun, no frontend, no sample
hardcoding, no `restructure`/`reject` silent promotion. `plan_composition()`
signature is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #5
Replace the hand-built Case 7 payload assertion with a temporary
production-source guard. The test now fails if Step 9 stops emitting
candidate_evidence, breaks the fallback_chain compat alias, or removes
the alias intent comment.
This is intentionally temporary because Step 9 application-plan unit
assembly is inline. Follow-up IMP-32 should extract a helper and replace
this source-string guard with a direct helper test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #5
- Add runtime template_id dedup in lookup_v4_match_with_fallback with
first-occurrence reservation; duplicate ranks become audit evidence,
not new fallback candidates.
- Add Step 9 candidate_evidence as the primary per-unit evidence field
while keeping fallback_chain as a compat alias for legacy readers.
- Add Step 20 fallback_selection_count and selection_paths derived from
comp_debug.v4_fallback_summary with defensive defaults; top-level
overall enum unchanged.
- Tighten synthetic fallback tests for duplicate handling (rank-1 reject A
+ rank-2 use_as_is A + rank-3 distinct B → rank-3 wins) and add tests
for candidate_evidence + alias equality and Step 20 qualifier presence
with defensive defaults.
- Verify with pytest (10 passed) and smoke_frame_render --self-check
(11/11 partials, IMP-04 F17 calibration intact).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same-frame F1 follow-up per Codex round 43 (#15527). matrix §4.1 Fix 7
4-class F1 path (no Track A pause, small fixes + Codex re-review).
Three fixes :
1. F1-a — explicit col_a/col_b label defaults
- Previous (c7b0f5b) used empty defaults `col_a_label_default: ""` /
`col_b_label_default: ""`, so an upstream MDX path without explicit
column headers would render blank header cells.
- Fix : set `col_a_label_default: "BIM"` and `col_b_label_default: "DX"`
in F18 contract. Frame intent is the BIM-vs-DX comparison, so the
headers are semantic and must not silently become blank.
2. F1-b — narrow prefix-stripping aliases (parser → builder option)
- Previous parser used a broad regex
`^[A-Za-z가-힣]{1,8}\s*:\s*(.+)$` to strip any short prefix before
`:`. That could accidentally remove meaningful Korean/English
prefixes from real cell content.
- Fix : remove auto-stripping from `parse_compare_row_2col_item`.
Stripping is now configurable via builder option
`strip_col_prefix_aliases: [<list of exact aliases>]` and applied
by `_build_compare_table_2col`. F18 contract uses `["BIM", "DX"]`,
so only `BIM:` / `DX:` (with optional fullwidth `:`) prefixes are
stripped; other Korean/English colons in real content stay intact.
- Parser signature unchanged. Builder is the single place that owns
the stripping policy.
3. F1-c — cardinality semantic clarification
- Previous top-level `cardinality.strict: 2` was ambiguous: it could
be read as `row strict: 2`. Rows are actually `1..12` via
`sub_zones.rows.cardinality`.
- Fix : add YAML comment that the top-level strict 2 = column count
(col_a / col_b), not row count. Per-sub_zone cardinality remains
authoritative for rows.
Verification :
- python -m py_compile src/phase_z2_mapper.py : PASS
- python scripts/smoke_frame_render.py --self-check : PASS 7/7 (F18
fixture rendered unchanged at 4211 chars; smoke harness only loaded
the partial, builder/parser logic not directly exercised in smoke)
- Manual builder/parser invocation test with synthetic units :
- col_a_label / col_b_label resolve to "BIM" / "DX" defaults.
- `BIM: Only 3D` → `Only 3D` (alias-stripped).
- `DX: BIM << DX (ENG. 포함)` → `BIM << DX (ENG. 포함)` (alias-stripped).
- `분야별 단절` → `분야별 단절` (no BIM/DX prefix, untouched).
- This matches the F1-b narrow-alias intent.
- python scripts/smoke_frame_render.py bim_dx_comparison_table
--render-to data/runs/imp04_f18_visual_r2 : PASS, R3 artifact written
with same character count, generic viewer title.
scope-lock honored : no V4 logic, no new builder/parser added (only
behavior refinement of existing F18 builder/parser/contract), no other
partial, no Phase Z production render, no Phase R'/AI/Kei changes.
4-class status (F18 post-F1) :
- class 1 readiness : adapter cleanup complete — defaults explicit,
aliases narrow, cardinality semantically clear.
- class 2 content-fit : still a watch item (long Korean wrapping,
6+ rows). max_rows=12 protection unchanged.
- class 3 / 4 : N/A.
Refs Gitea #4 (IMP-04 Track A frame 4 — F1 follow-up per Codex round 43)
Same-frame F1 follow-up commit per Codex round 37 (#15503). Matrix §4.1
Fix 7 4-class: F1 = small same-frame fix + Codex re-review (not F2/F3,
no Track A pause needed).
Two fixes (both Codex-caught) :
1. F1-a — `intersection` sub_zone contract semantics
- Previous (c67609c) declared `cardinality: { strict: 1 }` on the
intersection sub_zone, while the builder used `intersection_default: ""`
and the partial hides empty intersection. That mismatch meant
production payloads could silently omit the center concept while
the contract still demanded strict 1.
- Fix : `cardinality: { min: 0, max: 1 }` — intersection is now
explicitly optional, matching analysis.md "slots 5개, required 4개"
(intersection was originally the optional 5th slot).
- Builder behavior unchanged (intersection_default = "" still
produces empty when MDX has no center text; partial's :empty hide
remains semantically correct).
2. F1-b — smoke harness viewer title leakage
- Previous harness output had `<title>F14 render artifact — {template_id}</title>`
hardcoded from the original F14-specific implementation.
- Fix : generic `<title>Phase Z render artifact — {template_id}</title>`.
- Affects all `--render-to` dev artifacts going forward.
Verification :
- python scripts/smoke_frame_render.py --self-check : PASS 5/5
- python scripts/smoke_frame_render.py construction_goals_three_circle_intersection
--render-to data/runs/imp04_f12_visual_r2 : PASS, generated title now reads
"Phase Z render artifact — construction_goals_three_circle_intersection"
- Catalog yaml parse : intersection cardinality = {min: 0, max: 1}
scope-lock honored : no V4 logic, no builder/parser logic, no other
partial, no Phase Z production render, no Phase R'/AI/Kei changes. F1
fix scope only.
Refs Gitea #4 (IMP-04 Track A frame 2 — F1 follow-up per Codex round 37)
Re-apply the W1 wording clarifications previously agreed in the comment
thread. The originally executed wording commit `6d33884` was reverted by
Codex via `c807b2c` after Codex acknowledged that `ㄱ` for both Claude and
Codex is comment-only review (no source/doc edit). Both sides 100%
agreement is restored via Claude rounds 16/17/19 and Codex rounds 15/18.
This Claude-applied commit is the agreed re-application path (option A).
Six fixes applied to docs/architecture/IMP-04-FRAME-SUITABILITY-MATRIX.md :
1. §2 ACTIVE 4 frames table : add resource status (A/T/I/F/S) column.
F13 has no `assets/` directory (marked `-`). State that ACTIVE means
catalog-registered / runtime-active, not necessarily A+T+I+F+S
resource-complete.
2. §4 Track B : strengthen wording. Track B = visual-resource-pending
inside the 32-frame scope, **NOT exclusion / discard / scope
reduction**. Visual-resource acquisition timing is the only difference.
3. §4 Track B option (b) : minimal CSS partial from analysis/texts only
is **main path X, explicit temporary placeholder only**. Avoids the
frame 1 (`three_persona_benefits` 556b448) quality failure pattern.
4. §4.1 Track A priority : **execution ordering only, NOT scope filter**.
All 16 reusable-now frames remain activation targets. V4-weak 11
frames stay in scope. Builder family grouping = secondary ordering
allowed after first refinement calibration.
5. §4.1 Codex review granularity explicit : first refinement, new builder
pattern, ambiguous mapping, shared catalog/builder logic = Codex
review mandatory. Low-risk repeated frames may be grouped only after
pattern is proven.
6. §4.1 calibration failure modes F1/F2/F3 (Claude round 14 catch + Codex
round 15 (a) accepted) :
- F1 small styling/wording issue -> same-frame follow-up commit +
Codex re-review.
- F2 approach-level problem -> pause Track A, approach re-lock round,
retry refinement.
- F3 scope/resource problem -> reopen scope/resource axis, possibly
IMP-04b, block bulk activation until resolved.
History trace : `fe766f1` (matrix base) -> `6d33884` (Codex W1 execute,
wrong workflow) -> `c807b2c` (Codex revert + workflow lock symmetric)
-> this commit (Claude re-apply under user execution signal, option A).
History noise retained as honest trace of the corrected ㄱ definition.
No source code changes. Documentation clarification only.
Refs Gitea #4 (IMP-04 — matrix wording re-application post-revert)
- Add active-frame resource status and clarify catalog-active versus resource-complete.\n- Clarify Track B as visual-resource-pending inside the 32-frame scope, not exclusion.\n- Clarify Track A priority as execution order, not scope filter.\n- Add Codex review granularity and calibration failure modes for the first refinement.\n- Mark minimal CSS from analysis/texts as temporary placeholder only.
- Add scripts/smoke_frame_render.py for IMP-04 scope-lock §11+§13:
isolated StrictUndefined Jinja partial render gate, separated from
production render_slide() permissive behavior
- CLI: --self-check runs every bundled fixture; positional template_id
takes payload via --payload <json> / stdin / fixture
- Bundled mock payloads for the 3 existing active frames match real
builder output shape; all 3 partials PASS self-check
- New frame activation gate (per-frame 6-step Step 5): partial must
pass smoke render with a complete mock that mirrors the builder's
output dict (optional fields supplied as empty/falsy so {% if %}
guards still work under StrictUndefined)
- Exit codes: 0=all pass, 1=at least one fail, 2=invalid input
Latent finding (out of IMP-04 scope, surfaced for the record):
bim_issues_quadrant_four partial references slot_payload.quadrant_N_headline,
but _build_quadrant_flat_slots() only produces quadrant_N_label and
quadrant_N_body. The headline div therefore never renders in production.
Either dead reference or a builder extension that was never landed —
reconcile in a follow-up axis, not in IMP-04 catalog expansion.
production render path (phase_z2_pipeline.render_slide) unchanged.
Refs Gitea #4 (IMP-04 A-2 Catalog 확장 — infra commit)
- Classify pipeline.py as Archive-heavy with Salvage Candidate 0
- Record Phase Q/R'/T execution flow as outside Phase Z normal path
- Mark deterministic pipeline utilities as Reference Only
- Mark pipeline_context.py schema and lifecycle methods as Reference Only
- Surface §1 B-1 as indirect surface, not direct composition planner
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Classify §2.10 as Mixed with Salvage Candidate 0
- Mark utilities / matching helpers / Phase Q-2 filtering as Reference Only
- Record block_reference high-level entries as Phase R' reference path Archive
- Record block_selector format_candidates_for_prompt as Kei prompt formatter Archive
- Surface §1 A-2 indirect, catalog duplication, block_selector legacy as separate axes
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Classify html_generator.py as Archive-heavy with Salvage Candidate 0
- Record AI HTML generation path as Phase R' Archive Candidate
- Mark deterministic duplicate helpers as Reference Only
- Surface A-3/A-4 mapping mismatch as a factual note
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Classify renderer.py as Mixed with Salvage Candidate 0
- Mark Jinja/catalog/template rendering helpers as Reference Only
- Record render_slide_from_html Phase R' path as Archive Candidate
- Clarify A-3 as indirect preview render surface
- Clarify A-4 as slide-base impact surface without conditional CSS implementation
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Classify content_verifier.py as Mixed (Reference Only + Archive Candidate)
- Mark deterministic utilities (text extraction, Korean normalization, verification logic) as Reference Only — Phase Z normal path direct slot absent
- Mark Kei artifact detection (verify_no_forbidden_content + FORBIDDEN_KEI_MEMOS) and Kei AI generation retry loop (generate_with_retry) as Archive Candidate
- Clarify L4 Selenium / L5 Opus vision are header documentation only (no implementation in this file)
- Record no §3 Salvage application target (Salvage Candidate 0)
- Surface §1 B-2 mapping as indirect; H1-H3 reference axis candidates, H4-H5 Archive markers, H6 L4/L5 factual note
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Classify content_editor.py as Archive Candidate at module level
- Document Kei AI direct slot-fill flow (fill_content / fill_candidates / EDITOR_PROMPT)
- Mark Kei API infrastructure (_call_kei_editor_with_retry / _parse_json) as outside Phase Z normal path
- Record no §3 Salvage Plan target for this module
- Surface §1 B-1/B-2 candidate-file mapping mismatch and AI repair fallback infrastructure as separate axes
- Add §5-1 dated entry for 2026-05-12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
문서 역할 분담:
- README : 프로젝트 개요 + 큰 그림 (가끔 갱신)
- OVERVIEW : 22 단계 도면 (구조 잠금)
- STATUS-BOARD : 각 단계 정확한 상태 (자주 갱신)
- CHANGE-LOG : 결정 변경 이력
- ROADMAP (신규): 진행 계획 + 당장 할 일 + 세부 todo (자주 갱신)
내용:
1. 현재 위치 (MDX03 정상 경로 통과)
2. 지금까지 완료한 작업 6 가지 영역
3. 22 단계 진행 수준 (추정 % 표 + STATUS-BOARD 단일 출처 caveat)
4. 큰 로드맵 7 단계
5. 세부 todo (5.1 보고용 프로토타입 우선 / 5.2~5.8 단계별)
6. 당장 실행 순서 (보고용 프로토타입 → 22 단계 정리 복귀)
7. 문서 역할 분담 표
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 1. 프로젝트 개요 + 핵심 용어 표 (MDX / V4 / Phase Z / Frame / Region / Slot)
- 2. 22 단계 파이프라인 — 각 단계 별도 박스, 한글 라벨, 결정 분기 포함
- 3. 현재 완료 수준 — MDX03 정상 경로 통과, 진행 약 65~70%
- 4. 향후 로드맵 — 7 단계 (한글 LR Mermaid + 단계별 상세)
- 5. 운영 원칙 — 절대 원칙 / V4 ↔ Phase Z 책임 / 렌더 정책
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>