From 7762f6766acd3eac093c4ab78f3dc75d9931ac99 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Thu, 7 May 2026 11:19:52 +0900 Subject: [PATCH] Lock Phase Z overlay schema for Option E migration - categorize F13/F29/F16 frame_contracts fields as overlay-only, templates_v1-derived, or validation duplicate - lock duplicate hard-error, 1:1 keyspace, manual trigger, semantic-identical rollback - defer analysis.md direction inversion and 32-frame audit as separate axes --- docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md | 177 ++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md diff --git a/docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md b/docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md new file mode 100644 index 0000000..8801261 --- /dev/null +++ b/docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md @@ -0,0 +1,177 @@ +# Phase Z Overlay Schema (Option E first migration) + +**Status**: Step 0 schema lock — pre-execution spec. +**Scope**: F13 / F29 / F16 only (3 active Phase Z frames). +**Last updated**: 2026-05-07. + +--- + +## 0. Why this doc exists + +`templates/phase_z2/catalog/frame_contracts.yaml` is currently a hand-curated island. It must be hand-edited whenever a Phase Z frame's slot/sub_zone/payload structure changes, even though most of its sibling Phase Z artifacts (V4 matching results, `structure_ontology.yaml` `templates_v1` section, `figma_to_html_agent/blocks/{frame_id}/analysis.md`) are auto-generated from upstream sources. + +Option E first migration converts `frame_contracts.yaml` to a **generated artifact**: + +``` +runtime_overlay/{template_id}.yaml (per-template Phase Z fields, hand-edited) + + +templates_v1[frame_id] (cross-validation only in first migration) + ↓ +build_phase_z2_frame_contracts.py (generator) + ↓ +templates/phase_z2/catalog/frame_contracts.yaml (committed, generated) + ↓ +Phase Z runtime (unchanged — reads frame_contracts.yaml the same way) +``` + +This doc locks the schema/keyspace/trigger/rollback rules **before** any overlay file or generator code is written. + +--- + +## 0-1. Field owner table + +Each field in current `frame_contracts.yaml` is classified as one of: + +- **(a) templates_v1-derived** — generator looks up from `tests/matching/structure_ontology.yaml` `templates_v1` section. **Not declared in overlay.** +- **(b) overlay-only** — Phase Z 전용 operational config. **Declared in `runtime_overlay/{template_id}.yaml`.** +- **(c) validation duplicate** — generator cross-checks overlay value against `templates_v1`. Disagreement → hard error. + +### F13 — three_parallel_requirements (frame_id 1171281190) + +| Field | Current value | Category | Note | +|---|---|---|---| +| `template_id` | `three_parallel_requirements` | (c) | overlay filename = template_id; cross-check `templates_v1['1171281190'].template_id` | +| `frame_id` | `1171281190` | (b) | overlay declares; generator confirms it exists as a `templates_v1` key | +| `family` | `three_parallel` | (b) | Phase Z categorization. Not the same as `templates_v1.visual_pattern.family` (= `list`) — different semantic axis. Name overlap is incidental. | +| `source_shape` | `top_bullets` | (b) | Phase Z B1 extractor signal. Not in `templates_v1`. | +| `cardinality.strict` | `3` | (c) | cross-check against `templates_v1.visual_pattern.cardinality` — must equal `min` (and `max` if min==max). | +| `cardinality.overflow_policy` | `abort_or_review` | (b) | Phase Z fallback policy. | +| `role_order` | `[tech, people, nature]` | (b) | F13-specific visual role mapping. Not in `templates_v1`. | +| `visual_hints.min_height_px` | `230` | (b) | Phase Z layout calculation. Not in `templates_v1`. | +| `accepted_content_types` | `[text_block]` | (b) | SPEC v1 §3 Layer A→B input. Not in `templates_v1`. | +| `sub_zones` | `[pillar_1, pillar_2, pillar_3]` (with `partial_target_path`) | (b) | Phase Z Frame Slot declaration. Conceptually overlaps with `templates_v1.slots` (`pillar_*_label/body`) but different shape (sub_zones = column units; slots = label+body pairs). | +| `payload.*` | `{title, builder, builder_options}` | (b) | Phase Z mapper directives. Not in `templates_v1`. | + +### F29 — process_product_two_way (frame_id 1171281210) + +| Field | Current value | Category | Note | +|---|---|---|---| +| `template_id` | `process_product_two_way` | (c) | filename = template_id; cross-check | +| `frame_id` | `1171281210` | (b) | | +| `family` | `two_column_h3` | (b) | `templates_v1.visual_pattern.family` = `compare`; intentionally different | +| `source_shape` | `h3_subsections` | (b) | F29-specific B1 path | +| `cardinality.strict` | `2` | (c) | cross-check against `templates_v1.visual_pattern.cardinality.{ideal:2, min:2, max:2}` | +| `cardinality.overflow_policy` | `abort_or_review` | (b) | | +| `visual_hints.min_height_px` | `345` | (b) | | +| `accepted_content_types` | `[text_block, transform_table]` | (b) | F29 process column accepts AS-IS/TO-BE table | +| `sub_zones` | `[process_column, product_column]` (each `cardinality.strict: 3`) | (b) | 2 column × 3 sections; sub_zone unit = column | +| `payload.*` | `{title, builder=process_product_pair, builder_options}` | (b) | | + +### F16 — bim_issues_quadrant_four (frame_id 1171281193) + +| Field | Current value | Category | Note | +|---|---|---|---| +| `template_id` | `bim_issues_quadrant_four` | (c) | | +| `frame_id` | `1171281193` | (b) | | +| `family` | `bim_issues_quadrant` | (b) | `templates_v1.visual_pattern.family` = `cards` | +| `source_shape` | `top_bullets` | (b) | | +| `cardinality.strict` | **(not declared)** | n/a | F16 intentionally omits — uses `pad_to=4` + `truncate>4` policy in `payload.builder_options`. `templates_v1.cardinality` has `{ideal:4, min:4, max:4}` but Phase Z does not enforce strict here. **Generator must NOT auto-derive this from `templates_v1`.** | +| `accepted_content_types` | `[text_block]` | (b) | | +| `sub_zones` | `[quadrant_1, quadrant_2, quadrant_3, quadrant_4]` (each `cardinality.strict: 1`) | (b) | sub_zone-level cardinality = capacity; not the same as frame-level | +| `payload.*` | `{title, builder=quadrant_flat_slots, builder_options.{item_parser, pad_to, truncate_at, label_key_pattern, body_key_pattern, empty_label, empty_body}}` | (b) | | + +### Summary + +- **(a) templates_v1-derived**: **none** in first migration. Overlay declares all operational config. +- **(b) overlay-only**: ~all fields. Overlay file is essentially a per-template extract of the current `frame_contracts.yaml` entry. +- **(c) validation duplicate**: 2 fields per template — `template_id` (overlay filename matches `templates_v1[frame_id].template_id`) and `cardinality.strict` when present (must match `templates_v1.visual_pattern.cardinality.min`). + +**Implication**: first migration is mostly a **split-and-concatenate** refactor with light cross-validation. Migration value is (i) per-template editing isolation, (ii) `templates_v1` consistency check at build time. Heavier derivation (e.g., generating `cardinality.strict` from `templates_v1` automatically) is **별 axis** — defer until a concrete need surfaces. + +--- + +## 0-2. Duplicate rule — hard error + +If an overlay file declares a field that is classified as **(a) templates_v1-derived**, generator **fails immediately** with a clear message. No silent override semantics. + +Currently no fields are in (a), so this rule is dormant for first migration. It exists to prevent regression: a future overlay author cannot quietly duplicate a `templates_v1`-owned field without removing the (a) classification first. + +For **(c) validation duplicate** fields, the rule is: + +- Overlay declares the value. +- Generator looks up the corresponding value in `templates_v1`. +- If the two **disagree**, hard error with both values printed and a pointer to this doc. + +This preserves the lock-layer "overlay = single source of truth for declared values" while making `templates_v1` drift visible. + +--- + +## 0-3. Keyspace rule + +- **Source ref** = `frame_id` (Figma origin). +- **Overlay identity** = `template_id` (filename: `runtime_overlay/{template_id}.yaml`). +- **First-migration assumption**: `frame_id` ↔ `template_id` is **1:1** for all active Phase Z frames (F13/F29/F16). +- **Generator verification**: for each overlay, the generator looks up `templates_v1[overlay.frame_id].template_id` and asserts it equals the overlay filename's `{template_id}`. Mismatch → hard error. +- **Multi-variant** (one frame ↔ multiple templates) = **별 axis**. When that case appears, this doc is updated and the keyspace becomes a composite key. Until then, 1:1 is locked. + +--- + +## 0-4. Trigger + +- **Manual run** for first migration: + ``` + python scripts/build_phase_z2_frame_contracts.py + ``` +- **Verification** (semantic-identical) is **required** after each run. Generator should refuse to write output if the result differs semantically from current `frame_contracts.yaml` during first migration. +- **CI / pre-commit automation** = **별 axis**. Will be considered after first migration ships and a drift incident actually occurs. Adding it now is over-engineering for 3 templates. + +--- + +## 0-5. Rollback / failure path + +If `semantic-identical` verification fails (`yaml.safe_load(current) != yaml.safe_load(generated)`): + +1. **Generated artifact is NOT adopted.** No write to `frame_contracts.yaml`. +2. Current `frame_contracts.yaml` remains canonical (status quo preserved). +3. Diff source: overlay schema, generator implementation, or `templates_v1` cross-check rule. Identify root cause. +4. Fix overlay/schema/generator. Retry from Step 1. +5. **Migration is incomplete**: 3 active templates partially migrated count as failure — either all 3 ship or none. + +The migration commit (Step 5) is **only made after** all 3 templates pass both (a) semantic-identical and (b) Phase Z runtime regression (final.html identical). + +--- + +## 0-6. Scope boundary + +**Option E first migration solves**: +- `frame_contracts.yaml` island problem (hand-curated → generated). +- Per-template editing isolation. +- Light `templates_v1` consistency check at build time. + +**Option E first migration does NOT solve** (= 별 axis, NOT addressed by this migration): + +- `analysis.md` ↔ `structure_ontology.yaml` ownership direction conflict. Specifically: `figma_to_html_agent/CLAUDE.md` implies forward direction (agent owns `analysis.md`), while `tests/matching/sync_analysis_from_ontology.py` enforces reverse direction (`structure_ontology.yaml` is source, `analysis.md` is mirror). This is a separate architectural decision and is out of scope here. +- `templates/blocks/structures` legacy retirement. +- `legacy templates/catalog/blocks.yaml` removal. +- `zone_extract` rule formalization (long-term zone_application policy). +- 32-frame full audit (only active F13/F29/F16 in first migration). + +When the user resumes work on the direction inversion axis, this doc is **not invalidated** — overlay schema and generator continue to work because they read `templates_v1` (not `analysis.md`). + +--- + +## Migration steps (post-Step 0) + +1. Create `templates/phase_z2/catalog/runtime_overlay/{F13,F29,F16}.yaml` files. Each contains the (b) overlay-only fields + (c) validation duplicate values for one template. +2. Write `scripts/build_phase_z2_frame_contracts.py` generator. Reads overlays + `templates_v1`, applies 0-2 / 0-3 rules, writes generated `frame_contracts.yaml`. +3. Run generator. Verify `yaml.safe_load(current) == yaml.safe_load(generated)`. List order preserved (sub_zones especially). On disagreement → 0-5 rollback. +4. Run Phase Z pipeline once (regression MDX) — confirm `final.html` is byte-identical to current run. +5. Commit Option E migration (overlay files + generator + new generated `frame_contracts.yaml` with header). Separate commit from Step 0 schema doc. + +--- + +## Out of scope for this doc + +- Generator implementation details (parsing, emit format, error message templates) — captured in the script itself. +- Overlay file format details beyond field categorization — captured in the overlay files themselves. +- Direction inversion fixes — see future axis docs.