fix(release): fail closed on dashboard parse errors

This commit is contained in:
2026-07-22 17:07:30 +02:00
parent b6bef410d6
commit 753dd2a3ee
9 changed files with 306 additions and 15 deletions

View File

@@ -13,6 +13,7 @@ from pathlib import Path
from .model import (
CompatibilityIssue,
DashboardCollectionError,
InterfaceProviderSnapshot,
InterfaceRequirementSnapshot,
ModuleContractSnapshot,
@@ -33,6 +34,43 @@ def collect_contracts(repositories: tuple[RepositorySnapshot, ...]) -> tuple[Mod
return tuple(contracts)
def collect_contracts_with_diagnostics(
repositories: tuple[RepositorySnapshot, ...],
) -> tuple[tuple[ModuleContractSnapshot, ...], tuple[DashboardCollectionError, ...]]:
"""Collect readable contracts and retain bounded parse diagnostics for the UI."""
contracts: list[ModuleContractSnapshot] = []
diagnostics: list[DashboardCollectionError] = []
for repo in repositories:
if not repo.exists or not repo.is_git:
continue
root = Path(repo.absolute_path)
for manifest in sorted(root.glob("src/**/backend/manifest.py")):
try:
contract = parse_manifest_contract(manifest, repo_name=repo.spec.name)
except Exception as exc: # noqa: BLE001 - fail-closed UI diagnostic.
relative_source = manifest.relative_to(root).as_posix()
diagnostics.append(
DashboardCollectionError(
code="module_contract_unreadable",
message=(
"Module contract metadata could not be read or parsed "
f"({type(exc).__name__})."
),
remediation=(
"Repair the manifest's Python syntax or read permissions, "
"then refresh the dashboard before planning a release."
),
repo=repo.spec.name,
source=f"{repo.spec.name}/{relative_source}",
)
)
continue
if contract is not None:
contracts.append(contract)
return tuple(contracts), tuple(diagnostics)
def validate_contracts(contracts: tuple[ModuleContractSnapshot, ...]) -> tuple[CompatibilityIssue, ...]:
"""Validate the statically collected module interface graph.