From 1da0807ad838d7d5dc7ac962d1c8d1c9195fd4dd Mon Sep 17 00:00:00 2001 From: leefer Date: Sat, 1 Aug 2026 03:40:01 +0800 Subject: [PATCH] migration: prove standalone maintenance and correct visual evidence --- config/architecture-inventory.json | 14 +++++----- tests/preservation_helpers.py | 9 +++---- tests/test_frontend_boundaries.py | 9 +++++++ tests/test_maintenance_tools.py | 23 ++++++++++++++++- tools/README.md | 7 +++++ tools/build_architecture_inventory.py | 16 +++++++----- tools/verify_baseline.py | 37 +++++++++++++++++++++++++-- 7 files changed, 93 insertions(+), 22 deletions(-) diff --git a/config/architecture-inventory.json b/config/architecture-inventory.json index 6577d3d..9d0aa96 100644 --- a/config/architecture-inventory.json +++ b/config/architecture-inventory.json @@ -254,7 +254,7 @@ "code_hotspots": [ { "path": "frontend/styles/styles.css", - "bytes": 361780, + "bytes": 361776, "lines": 15465 }, { @@ -279,12 +279,12 @@ }, { "path": "frontend/app.js", - "bytes": 91151, + "bytes": 89213, "lines": 1939 }, { "path": "frontend/pages/heaven/page.js", - "bytes": 88322, + "bytes": 86493, "lines": 1830 }, { @@ -299,7 +299,7 @@ }, { "path": "backend/features/heaven/service.py", - "bytes": 64421, + "bytes": 63123, "lines": 1303 }, { @@ -309,7 +309,7 @@ }, { "path": "frontend/pages/market/runtime.js", - "bytes": 57053, + "bytes": 55720, "lines": 1333 }, { @@ -319,7 +319,7 @@ }, { "path": "backend/application.py", - "bytes": 50934, + "bytes": 49784, "lines": 1165 }, { @@ -329,7 +329,7 @@ }, { "path": "database.py", - "bytes": 34013, + "bytes": 33284, "lines": 746 } ] diff --git a/tests/preservation_helpers.py b/tests/preservation_helpers.py index 0e29cd7..308dfeb 100644 --- a/tests/preservation_helpers.py +++ b/tests/preservation_helpers.py @@ -26,6 +26,7 @@ RETIRED_FRONTEND_SOURCE_RANGES = ( (9022, 9027), (9052, 9055), ) +AUDITED_FRONTEND_SOURCE_LINE_COUNT = 9283 def sha256(path: Path) -> str: @@ -51,12 +52,10 @@ def reassembled_frontend_runtime() -> str: assembled.append(content) next_line = end + 1 - original_line_count = len( - (ORIGINAL_STATIC / "app.js").read_text(encoding="utf-8").splitlines() - ) - if next_line != original_line_count + 1: + if next_line != AUDITED_FRONTEND_SOURCE_LINE_COUNT + 1: raise AssertionError( - f"app.js source coverage ended at {next_line - 1}, expected {original_line_count}" + "app.js source coverage ended at " + f"{next_line - 1}, expected {AUDITED_FRONTEND_SOURCE_LINE_COUNT}" ) return "".join(assembled) diff --git a/tests/test_frontend_boundaries.py b/tests/test_frontend_boundaries.py index 272b09e..763b411 100644 --- a/tests/test_frontend_boundaries.py +++ b/tests/test_frontend_boundaries.py @@ -4,6 +4,7 @@ import json import re import unittest from pathlib import Path +from unittest.mock import patch from tests.preservation_helpers import reassembled_frontend_runtime @@ -46,6 +47,14 @@ class FrontendBoundaryTests(unittest.TestCase): self.assertIn("const state = window.XiaobaiState.create({", app) self.assertNotIn("const state = {", app) + def test_candidate_runtime_reassembly_does_not_require_original_static(self) -> None: + with patch( + "tests.preservation_helpers.ORIGINAL_STATIC", + ROOT / "missing-original-static", + ): + app = reassembled_frontend_runtime() + self.assertIn("function openView(", app) + def test_runtime_page_registry_matches_governance_registry(self) -> None: expected = json.loads( (ROOT / "config" / "pages.config.json").read_text(encoding="utf-8") diff --git a/tests/test_maintenance_tools.py b/tests/test_maintenance_tools.py index 169ced0..36b14cc 100644 --- a/tests/test_maintenance_tools.py +++ b/tests/test_maintenance_tools.py @@ -3,11 +3,16 @@ from __future__ import annotations import json import subprocess import sys +import tempfile import unittest from pathlib import Path from tools.build_api_registry import build as build_api_registry -from tools.build_architecture_inventory import build as build_architecture_inventory +from tools.build_architecture_inventory import ( + build as build_architecture_inventory, + source_metrics, +) +from tools.verify_baseline import python_test_command ROOT = Path(__file__).resolve().parents[1] @@ -34,6 +39,22 @@ class MaintenanceToolTests(unittest.TestCase): self.assertIn("stop_e2e_server(server)", source) self.assertNotIn('"static/app.js"', source) + def test_standalone_verifier_excludes_only_migration_comparison_modules(self) -> None: + repository_command = python_test_command(preservation_baseline=True) + standalone_command = python_test_command(preservation_baseline=False) + self.assertIn("discover", repository_command) + self.assertTrue(any(item == "tests.test_frontend_contract" for item in standalone_command)) + self.assertFalse(any("test_preservation_" in item for item in standalone_command)) + + def test_architecture_metrics_are_independent_of_checkout_line_endings(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + lf = root / "lf.js" + crlf = root / "crlf.js" + lf.write_bytes(b"const a = 1;\nconst b = 2;\n") + crlf.write_bytes(b"const a = 1;\r\nconst b = 2;\r\n") + self.assertEqual(source_metrics(lf), source_metrics(crlf)) + def test_every_tool_has_a_non_mutating_help_path(self) -> None: for path in sorted((ROOT / "tools").glob("*.py")): if path.name.startswith("_"): diff --git a/tools/README.md b/tools/README.md index 6f4462e..f1e6397 100644 --- a/tools/README.md +++ b/tools/README.md @@ -14,6 +14,13 @@ maintenance command cannot be mistaken for a historical migration rewrite. - `python tools/build_architecture_inventory.py [--check]`: generate or verify `config/architecture-inventory.json` from the candidate source tree. +Inside the canonical `webapp/app/` checkout, `verify_baseline.py` runs the full preservation +suite against the retained original baseline and enforces `git diff --check`. In a standalone +`app/` export where that baseline and Git checkout do not exist, the same command runs all +candidate-owned tests, skips only `test_preservation_*` comparison modules, and reports the Git +check as skipped. Product, registry, JavaScript, database, and optional Playwright checks remain +active in both modes. + ## Acceptance and differential checks - `run_preservation_runtime.py`: start an isolated original or candidate runtime with an diff --git a/tools/build_architecture_inventory.py b/tools/build_architecture_inventory.py index bccec7f..0ee1e3e 100644 --- a/tools/build_architecture_inventory.py +++ b/tools/build_architecture_inventory.py @@ -75,6 +75,14 @@ def css_layers(html: str) -> list[str]: return re.findall(r']+rel="stylesheet"[^>]+href="([^"]+)"', html) +def source_metrics(path: Path) -> dict[str, int]: + text = path.read_text(encoding="utf-8") + return { + "bytes": len(text.encode("utf-8")), + "lines": len(text.splitlines()), + } + + def code_hotspots() -> list[dict[str, Any]]: candidates = [ "backend/application.py", @@ -99,13 +107,7 @@ def code_hotspots() -> list[dict[str, Any]]: path = ROOT / name if not path.is_file(): continue - rows.append( - { - "path": name, - "bytes": path.stat().st_size, - "lines": len(path.read_text(encoding="utf-8").splitlines()), - } - ) + rows.append({"path": name, **source_metrics(path)}) return sorted(rows, key=lambda item: item["bytes"], reverse=True) diff --git a/tools/verify_baseline.py b/tools/verify_baseline.py index 9bf656c..bbc571b 100644 --- a/tools/verify_baseline.py +++ b/tools/verify_baseline.py @@ -21,6 +21,39 @@ def run(label: str, command: list[str]) -> None: subprocess.run(command, cwd=ROOT, check=True) +def python_test_command(preservation_baseline: bool | None = None) -> list[str]: + if preservation_baseline is None: + preservation_baseline = (ROOT.parent / "static" / "app.js").is_file() + if preservation_baseline: + return [sys.executable, "-m", "unittest", "discover", "-s", "tests"] + modules = [ + f"tests.{path.stem}" + for path in sorted((ROOT / "tests").glob("test_*.py")) + if not path.stem.startswith("test_preservation_") + ] + if not modules: + raise RuntimeError("no standalone candidate tests found") + return [sys.executable, "-m", "unittest", *modules] + + +def verify_git_diff() -> None: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + print("\n[patch] skipped: standalone export is not a Git checkout") + return + repository_root = Path(result.stdout.strip()).resolve() + if ROOT != repository_root / "app": + print("\n[patch] skipped: standalone export is outside the canonical app path") + return + run("patch", ["git", "diff", "--check"]) + + def verify_database() -> None: database = ROOT / "data" / "review.db" if not database.exists(): @@ -97,7 +130,7 @@ def main() -> int: ) args = parser.parse_args() - run("python", [sys.executable, "-m", "unittest", "discover", "-s", "tests"]) + run("python", python_test_command()) run( "api-registry", [sys.executable, "tools/build_api_registry.py", "--check"], @@ -117,7 +150,7 @@ def main() -> int: "javascript", [node, "--check", script.relative_to(ROOT).as_posix()], ) - run("patch", ["git", "diff", "--check"]) + verify_git_diff() verify_database() if args.e2e: