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>