docs(IMP-52): reconcile Phase Z family count drift -- F-2 option (c)

Audit follow-up F-2 (INTEGRATION-AUDIT-01 §10.2). Phase Z families surface
showed 11 tracked / 11 contracted / 13 on disk. The 2 untracked WIP files
(app_sw_package_vs_solution.html, pre_construction_model_info_stacked.html)
are now declared in _WIP_FILES.md as uncontracted and out-of-scope for the
runtime matcher; promote/remove is gated on #42. The 11/11 tracked +
contracted baseline is unchanged. A new pytest enforces tracked families ↔
frame_contracts.yaml set-equality modulo the WIP allowlist parsed from
_WIP_FILES.md, so future drift fails fast in CI before #42 expands to 32
frames.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 19:15:04 +09:00
parent 191b6a9d85
commit 8f06a4c99f
4 changed files with 128 additions and 5 deletions

View File

@@ -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 "<svg|viewBox" templates/phase_z2/`**0 matches** (exit 1).
@@ -48,7 +48,7 @@ Per `CLAUDE.md` Phase R' regression prevention rules and the Stage 1/2 exit repo
- `src/renderer.py` — read-only. No edit to `_preprocess_svg_data` body, `SVG_BLOCKS` set, or `render_multi_page` call site.
- `src/svg_calculator.py` — read-only. No edit to the five helpers or their public signatures.
- `templates/phase_z2/families/*.html` (13) + `templates/phase_z2/frames/*.html` (2) — no `<svg>` / `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 `<svg>` / `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.

View File

@@ -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

View File

@@ -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)

View File

@@ -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."
)