fix(release): prepare aligned 0.1.45 composition and guarded candidate sequencing

This commit is contained in:
2026-09-08 01:52:08 +02:00
parent 1cec4ee1d8
commit 6a8f53b87d
17 changed files with 588 additions and 72 deletions
@@ -39,6 +39,9 @@ def tag_repositories(
A release tag is only created for a clean, aligned, non-behind worktree.
Both local and remote tags are resolved to commits before mutation so an
existing immutable tag can never be moved by this operation.
Module-only local candidate tags precede Core's release-lock regeneration;
their cross-Core composition gate applies before publication, not creation.
Core candidate tags still require a complete aligned release bundle.
"""
workspace = resolve_workspace_root(workspace_root)
@@ -47,13 +50,19 @@ def tag_repositories(
selected = tuple(dict.fromkeys(repos))
results: list[dict[str, object]] = []
bundle_issues_by_repo: dict[str, list[str]] = {}
for issue in selected_release_webui_bundle_issues(
repo_versions={repo: repo_versions.get(repo, "") for repo in selected},
workspace=workspace,
):
bundle_issues_by_repo.setdefault(issue.repo, []).append(
f"{issue.source}={issue.actual!r}, expected {issue.expected!r} ({issue.message})"
)
# Core's final lock is generated from reviewed local module tags. Requiring
# that lock before those tags exist makes the documented sequence circular.
# This is only a local module staging exception: Core-selected batches and
# every publication still run the cross-repository gate, and each selected
# repository's own version/lock checks below are always enforced.
if push or "govoplan-core" in selected:
for issue in selected_release_webui_bundle_issues(
repo_versions={repo: repo_versions.get(repo, "") for repo in selected},
workspace=workspace,
):
bundle_issues_by_repo.setdefault(issue.repo, []).append(
f"{issue.source}={issue.actual!r}, expected {issue.expected!r} ({issue.message})"
)
if apply:
preflight = tag_repositories(
@@ -269,6 +269,7 @@ def _render_python_version(
except SyntaxError as exc:
raise VersionMetadataError(f"Python metadata is malformed: {path.name}") from exc
values: list[ast.Constant] = []
module_version_references = 0
for node in ast.walk(tree):
if not isinstance(node, ast.Call) or _call_name(node.func) != target_name:
continue
@@ -279,6 +280,28 @@ def _render_python_version(
and isinstance(keyword.value.value, str)
):
values.append(keyword.value)
elif (
keyword.arg == keyword_name
and isinstance(keyword.value, ast.Name)
and keyword.value.id == "MODULE_VERSION"
):
module_version_references += 1
if module_version_references:
if module_version_references != 1 or values:
raise VersionMetadataError(
f"Python metadata has multiple {target_name}.{keyword_name} values: {path.name}"
)
rendered, found = _render_python_assignment(
payload,
path=path,
assignment_name="MODULE_VERSION",
version=version,
)
if not found:
raise VersionMetadataError(
f"Python metadata has no literal MODULE_VERSION declaration: {path.name}"
)
return rendered, True
if not values:
return payload, False
if len(values) != 1: