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
This commit is contained in:
177
docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md
Normal file
177
docs/architecture/PHASE-Z-OVERLAY-SCHEMA.md
Normal file
@@ -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.
|
||||||
Reference in New Issue
Block a user