From b6bef410d6a0dac70f1faf32f11df7823a389f8d Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 22 Jul 2026 16:55:06 +0200 Subject: [PATCH] fix(release): fail closed on malformed WebUI metadata --- tests/test_release_plan_guidance.py | 31 ++++++++++++++++++ .../govoplan_release/selective_planner.py | 32 ++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/tests/test_release_plan_guidance.py b/tests/test_release_plan_guidance.py index 1dce283..eb16c57 100644 --- a/tests/test_release_plan_guidance.py +++ b/tests/test_release_plan_guidance.py @@ -108,6 +108,37 @@ class ReleasePlanGuidanceTests(unittest.TestCase): self.assertIn("Repair the malformed TOML or JSON", finding.remediation) self.assertEqual("resolve_release_gate", plan.recommended_action.id) + def test_malformed_webui_metadata_is_a_structured_blocker(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + workspace = Path(temp_dir) + repo_path = workspace / "govoplan-files" + (repo_path / "webui").mkdir(parents=True) + (repo_path / "pyproject.toml").write_text( + '[project]\nname = "govoplan-files"\nversion = "1.2.4"\n', + encoding="utf-8", + ) + (repo_path / "webui" / "package.json").write_text( + "{not-json\n", encoding="utf-8" + ) + + plan = build_selective_release_plan( + dashboard(workspace=workspace, version="1.2.4"), + selected_repos=("govoplan-files",), + repo_versions={"govoplan-files": "1.2.4"}, + ) + + self.assertEqual("blocked", plan.status) + codes = {item.code for item in plan.gate_findings} + self.assertIn("repository_version_metadata_unreadable", codes) + finding = next( + item + for item in plan.gate_findings + if item.code == "release_webui_metadata_unreadable" + ) + self.assertIn("JSONDecodeError", finding.message) + self.assertIn("Repair malformed JSON", finding.remediation) + self.assertEqual("resolve_release_gate", plan.recommended_action.id) + def test_webui_renders_recommendation_and_remediation(self) -> None: webui = (RELEASE_ROOT / "webui" / "index.html").read_text(encoding="utf-8") diff --git a/tools/release/govoplan_release/selective_planner.py b/tools/release/govoplan_release/selective_planner.py index ca3044c..fd79ce3 100644 --- a/tools/release/govoplan_release/selective_planner.py +++ b/tools/release/govoplan_release/selective_planner.py @@ -154,10 +154,34 @@ def apply_release_webui_bundle_gate( workspace: Path, ) -> tuple[ReleasePlanUnit, ...]: issues_by_repo: dict[str, list[ReleaseGateFinding]] = {} - for issue in selected_release_webui_bundle_issues( - repo_versions={unit.repo: unit.target_version for unit in units}, - workspace=workspace, - ): + try: + issues = selected_release_webui_bundle_issues( + repo_versions={unit.repo: unit.target_version for unit in units}, + workspace=workspace, + ) + except Exception as exc: # noqa: BLE001 - source errors become release findings. + for unit in units: + issues_by_repo.setdefault(unit.repo, []).append( + ReleaseGateFinding( + code="release_webui_metadata_unreadable", + severity="blocker", + message=( + "Release WebUI composition metadata could not be read or parsed " + f"({type(exc).__name__})." + ), + remediation=( + "Repair malformed JSON in the selected module WebUI package or " + "Core's release package/lockfile, refresh the repository " + "dashboard, then rebuild this plan." + ), + repo=unit.repo, + source="release WebUI composition metadata", + expected=unit.target_version, + actual="unreadable", + ) + ) + issues = () + for issue in issues: issues_by_repo.setdefault(issue.repo, []).append( ReleaseGateFinding( code="release_webui_composition_alignment",