diff --git a/docs/architecture/IMP-18-SVG-GAP-REPORT.md b/docs/architecture/IMP-18-SVG-GAP-REPORT.md index c9ee807..cecb061 100644 --- a/docs/architecture/IMP-18-SVG-GAP-REPORT.md +++ b/docs/architecture/IMP-18-SVG-GAP-REPORT.md @@ -25,9 +25,9 @@ Phase R' implements SVG coordinate pre-compute as a renderer hook. References (d Phase Z active partials surface: -- `templates/phase_z2/families/*.html` — **13** files. +- `templates/phase_z2/families/*.html` — **11 contracted + 2 WIP untracked = 13 on disk** (contracted set = `templates/phase_z2/catalog/frame_contracts.yaml` top-level keys; WIP allowlist = [`templates/phase_z2/families/_WIP_FILES.md`](../../templates/phase_z2/families/_WIP_FILES.md), gated on Gitea #42 / #52 F-2 option (c)). - `templates/phase_z2/frames/*.html` — **2** files. -- Total surface = **15 partials**. +- Total surface = **13 active partials (11 contracted families + 2 frames) + 2 WIP untracked families** (15 on disk; runtime matcher consumes the contracted set only). SVG usage scan (evidence): `rg "` / `viewBox` insertion in IMP-18 scope. SVG-bearing partial onboarding is owned by IMP-04. +- `templates/phase_z2/families/*.html` (11 contracted + 2 WIP untracked = 13 on disk; WIP set = [`_WIP_FILES.md`](../../templates/phase_z2/families/_WIP_FILES.md)) + `templates/phase_z2/frames/*.html` (2) — no `` / `viewBox` insertion in IMP-18 scope. SVG-bearing partial onboarding is owned by IMP-04. The 2 WIP family templates are gated on Gitea #42 (promote-or-remove) and remain outside the runtime matcher set per #52 F-2 option (c). - F12 `construction_goals_three_circle_intersection.html` HTML/CSS → SVG migration is **out of scope** (separate post-IMP-04 issue). - No hardcoded SVG coordinates in Phase Z templates — when IMP-18 re-activates, coordinates must be derived from `svg_calculator` helpers (or equivalent forward-port into `phase_z2_renderer`), not hand-copied. diff --git a/docs/architecture/INTEGRATION-AUDIT-01-REPORT.md b/docs/architecture/INTEGRATION-AUDIT-01-REPORT.md index acaa417..43eb4b5 100644 --- a/docs/architecture/INTEGRATION-AUDIT-01-REPORT.md +++ b/docs/architecture/INTEGRATION-AUDIT-01-REPORT.md @@ -175,7 +175,7 @@ Axis 3 (Section 5) will verify each pair has agreeing producer-line / consumer-l - 6 invariant categories evaluated. All AGREE for the closed-issue audit scope. - 2 surface notes recorded as Section 10 follow-up candidates : - **F-1** : issue body cites `src/phase_z2_mapper.py` for invariant C3 (`fit_classification`), but the live producer is `src/phase_z2_classifier.py`. Record-keeping correction needed in any future audit charter, not a code conflict. RESOLVED via IMP-53 (2026-05-19) - - **F-2** : 2 untracked family templates exist on disk without `frame_contracts.yaml` entries; IMP-18 doc cites "families/*.html (13)" forward-looking. Tracked baseline (11 / 11) is consistent. Contract drift is *not* present for any closed issue; the WIP delta belongs to open work. + - **F-2** : 2 untracked family templates exist on disk without `frame_contracts.yaml` entries; IMP-18 doc cites "families/*.html (13)" forward-looking. Tracked baseline (11 / 11) is consistent. Contract drift is *not* present for any closed issue; the WIP delta belongs to open work. RESOLVED via #52 option (c) (2026-05-19) -- WIP allowlist captured in `templates/phase_z2/families/_WIP_FILES.md`; tracked + contracted baseline unchanged at 11/11; promote / remove gated on #42. - 1 documented partial recorded : - Step 21 `_write_step_artifact` at `pipeline.py:4772` carries `step_status="partial"` with note `region marker partial 미주입 -- Step 21 ⚠ partial`. This is *self-honest acknowledged* per `feedback_artifact_status_naming`; no cross-issue conflict. - Phase R' <-> Phase Z boundary clean both directions for the 22 closed issues. @@ -455,7 +455,7 @@ Five candidates were produced by Axes 1-4. F-3 + F-2 + F-1 are blocking conditio - Live producer site : `src/phase_z2_classifier.py:495-497` (return dict with `visual_check_passed`, `classifications`, `summary`, `categories_seen`, `unclassified_signals`, `placement_diagnostics`). - **priority / gating** : low priority on its own; required for charter cleanliness; **not** a blocker for #19 Stage 2. -### 10.2 F-2 -- family template count reconciliation : 11 tracked / 11 contracted / 13 on disk +### 10.2 F-2 -- family template count reconciliation : 11 tracked / 11 contracted / 13 on disk -- RESOLVED via #52 (option c, 2026-05-19) - **title** : `[FAMILY-TEMPLATE-RECONCILE] templates/phase_z2/families/ has 13 .html files on disk but 11 tracked + 11 frame_contracts entries; 2 WIP files (app_sw_package_vs_solution.html, pre_construction_model_info_stacked.html) untracked` - **source_axis** : Axis 3 (invariant category C6 template / catalog / frame count) -- recorded in §5.2 C6 row + §5.4 follow-up bullet F-2 + §6.3 Axis 4 cross-axis consistency bullet. @@ -468,6 +468,12 @@ Five candidates were produced by Axes 1-4. F-3 + F-2 + F-1 are blocking conditio - REPORT §5.5 row "C6 family templates -- on disk" (`ls templates/phase_z2/families/*.html` = 13). - REPORT §6.3 Axis 4 cross-axis consistency bullet (matches IMP-04 evidence). - **priority / gating** : **must land before #19 introduces any new family template** (per §9.3 condition 2). Until #19's catalog touch surface is known, this can be filed independently. +- **resolution** : option (c) -- 2 WIP family templates explicitly noted as in-progress and tracked outside `frame_contracts.yaml` (RESOLVED via Gitea #52, 2026-05-19) : + - WIP allowlist : `templates/phase_z2/families/_WIP_FILES.md` (added by #52 u1) -- names both files with Figma frame IDs (`app_sw_package_vs_solution.html` -> frame 23 / `1171281203`; `pre_construction_model_info_stacked.html` -> frame 9 / `1171281180`) and explicit "not in `frame_contracts.yaml`, not in runtime matcher set" status; promote / remove gated on Gitea #42. + - IMP-18 doc reconciled : `docs/architecture/IMP-18-SVG-GAP-REPORT.md` L28 + L30 + L51 corrected from disk-only "13 files" / "15 partials" wording to "11 contracted + 2 WIP untracked = 13 on disk" (#52 u2) -- runtime matcher consumes the contracted set only; doc / tracked / contracted surfaces agree at 11 active. + - baseline guard (planned by #52 u4) : `tests/test_family_contract_baseline.py` will enforce tracked families <-> `frame_contracts.yaml` 1:1 set-equality modulo WIP allowlist parsed from `_WIP_FILES.md`; future drift (#42 or otherwise) fails CI. + - tracked baseline (11 contracted families <-> 11 `frame_contracts.yaml` entries) unchanged; no contract entries added or removed; no runtime matcher mutation; **C6 invariant remains AGREE** for the closed-issue audit scope. + - **F-2 closed-by-#52** under [[feedback_workflow_atomicity_rules]] (one commit = one decision unit), without re-opening any §5 C-invariant or §6.3 Axis 4 conclusion. #19 catalog-touch gate (per §9.3 condition 2) is now satisfied for the current 11/11 baseline; any #19 / #42 catalog growth must reconcile the WIP allowlist before merge. ### 10.3 F-3 -- backlog status sweep : 15 rows pending->implemented + 1 row pending->documented(deferred) + IMP-15 children footnote diff --git a/templates/phase_z2/families/_WIP_FILES.md b/templates/phase_z2/families/_WIP_FILES.md new file mode 100644 index 0000000..00abfca --- /dev/null +++ b/templates/phase_z2/families/_WIP_FILES.md @@ -0,0 +1,37 @@ +# Phase Z Families — WIP Marker + +**Status:** intentionally untracked, uncontracted, out-of-scope for runtime matcher. +**Closes audit follow-up:** INTEGRATION-AUDIT-01-REPORT.md §10.2 F-2 (option c). +**Gate for promote/remove:** Gitea issue #42 (`IMP-04b Catalog extension to 32 frames`). + +## Baseline lock (2026-05-19) + +| Surface | Count | Notes | +|---|---|---| +| `git ls-files templates/phase_z2/families/*.html` | 11 | tracked family templates | +| `templates/phase_z2/catalog/frame_contracts.yaml` top-level keys | 11 | 1:1 with tracked basenames | +| `Get-ChildItem templates/phase_z2/families/*.html` | 13 | tracked 11 + WIP 2 (below) | + +Active contracted family count = **11**. Tracked basenames ↔ `frame_contracts.yaml` top-level keys are set-equal. Drift between disk (13) and contracted (11) is fully explained by the 2 WIP files below. + +## WIP family templates (uncontracted) + +| File | Figma frame | Status | +|---|---|---| +| `app_sw_package_vs_solution.html` | frame 23 (`1171281203`) | WIP — not in `frame_contracts.yaml`, not in runtime matcher set. | +| `pre_construction_model_info_stacked.html` | frame 9 (`1171281180`) | WIP — not in `frame_contracts.yaml`, not in runtime matcher set. | + +These files are partials authored during Phase Z-2 MVP-1.5b exploration. They are **not** part of the contracted Phase Z runtime catalog and must not be enumerated by frame selection, matcher, or any Stage 3 pipeline surface. + +## Rules + +- Adding a file to `templates/phase_z2/families/*.html` without a matching `frame_contracts.yaml` entry is **only** permitted if it is named here as WIP. +- `tests/test_family_contract_baseline.py` enforces this invariant: tracked families ↔ `frame_contracts.yaml` keys must be set-equal, modulo the WIP allowlist parsed from this file. +- Promoting a WIP file (add `frame_contracts.yaml` entry + register with matcher) or removing it must happen under issue #42 or a follow-up issue, not silently. + +## References + +- `docs/architecture/INTEGRATION-AUDIT-01-REPORT.md` §10.2 F-2 (audit finding closed by issue #52, option c) +- `docs/architecture/IMP-18-SVG-GAP-REPORT.md` L28, L51 (count basis corrected to `11 contracted + 2 WIP`) +- Gitea issue #52 (this reconciliation) +- Gitea issue #42 (pre-flight gate for promote/remove) diff --git a/tests/test_family_contract_baseline.py b/tests/test_family_contract_baseline.py new file mode 100644 index 0000000..f1cdf9e --- /dev/null +++ b/tests/test_family_contract_baseline.py @@ -0,0 +1,80 @@ +"""Phase Z family-template ↔ frame_contracts.yaml baseline invariant. + +#52 F-2 option (c) lock (2026-05-19) — locks active contracted family count +at 11/11 with a WIP allowlist for the 2 untracked WIP family templates +documented in `templates/phase_z2/families/_WIP_FILES.md`. Any drift after +this point (new family file on disk without a contract entry, or a contract +entry pointing to a missing file) fails fast in CI. + +References: + - docs/architecture/INTEGRATION-AUDIT-01-REPORT.md §10.2 F-2 + - docs/architecture/IMP-18-SVG-GAP-REPORT.md L28/L30/L51 + - templates/phase_z2/families/_WIP_FILES.md (WIP allowlist source) + - Gitea #52 (this reconciliation), #42 (promote/remove gate) + +Pattern mirrors `tests/test_catalog_invariant.py` — fail fast with explicit +diff message if family ↔ contract surfaces drift. +""" +from __future__ import annotations + +import re +from pathlib import Path + +import yaml + +PROJECT_ROOT = Path(__file__).parent.parent +FAMILIES_DIR = PROJECT_ROOT / "templates" / "phase_z2" / "families" +CATALOG_PATH = PROJECT_ROOT / "templates" / "phase_z2" / "catalog" / "frame_contracts.yaml" +WIP_DOC_PATH = FAMILIES_DIR / "_WIP_FILES.md" + + +def _load_contract_keys() -> set[str]: + with CATALOG_PATH.open(encoding="utf-8") as f: + catalog = yaml.safe_load(f) + return {k for k, v in catalog.items() if isinstance(v, dict)} + + +def _load_disk_family_stems() -> set[str]: + return {p.stem for p in FAMILIES_DIR.glob("*.html")} + + +def _load_wip_allowlist() -> set[str]: + text = WIP_DOC_PATH.read_text(encoding="utf-8") + return {m.group(1) for m in re.finditer(r"`([A-Za-z0-9_\-]+)\.html`", text)} + + +def test_contracts_set_equals_disk_families_minus_wip(): + """`frame_contracts.yaml` keys ↔ disk family stems minus WIP allowlist.""" + contracts = _load_contract_keys() + disk = _load_disk_family_stems() + wip = _load_wip_allowlist() + expected = disk - wip + missing = expected - contracts + extra = contracts - expected + assert not missing, ( + f"Family files on disk without frame_contracts.yaml entry " + f"(and not in _WIP_FILES.md): {sorted(missing)}. " + "Add a contract entry, or list the file in " + "templates/phase_z2/families/_WIP_FILES.md as WIP." + ) + assert not extra, ( + f"frame_contracts.yaml has entries with no matching family file: " + f"{sorted(extra)}." + ) + + +def test_wip_allowlist_is_disk_only_and_uncontracted(): + """WIP allowlist names must exist on disk AND have no contract entry.""" + contracts = _load_contract_keys() + disk = _load_disk_family_stems() + wip = _load_wip_allowlist() + missing_on_disk = wip - disk + leaked_into_contracts = wip & contracts + assert not missing_on_disk, ( + f"_WIP_FILES.md names files not on disk: {sorted(missing_on_disk)}." + ) + assert not leaked_into_contracts, ( + f"_WIP_FILES.md names files that already have a contract entry: " + f"{sorted(leaked_into_contracts)}. Promote via #42 instead — " + "WIP allowlist must be disk-only / uncontracted." + )