Harden source release preparation and record verified security follow-up
Dependency Audit / dependency-audit (push) Successful in 1m51s
Deployment Installer / deployment-installer (push) Successful in 8s
Security Audit / security-audit (push) Successful in 12m32s

This commit is contained in:
2026-09-08 08:04:12 +02:00
parent 9554657bb5
commit 58d320d9b3
26 changed files with 3833 additions and 163 deletions
@@ -8,6 +8,7 @@ from pathlib import Path
import shlex
from .contracts import validate_contracts
from .git_state import registered_developer_meta_path, read_pyproject_version
from .model import (
CompatibilityIssue,
InterfaceProviderSnapshot,
@@ -111,6 +112,39 @@ def apply_repository_version_gate(
version_update_supported_by_repo: dict[str, bool] = {}
deferred_core_lock_repos: set[str] = set()
for unit in units:
if registered_developer_meta_path(workspace / unit.repo) is not None:
from .meta_preparation import preparation_command
try:
core_version = read_pyproject_version(workspace / "govoplan-core")
except (OSError, ValueError, TypeError):
core_version = None
core_ready = core_version == unit.target_version
issues_by_repo.setdefault(unit.repo, []).append(
ReleaseGateFinding(
code="developer_meta_out_of_run" if core_ready else "developer_meta_core_preparation_required",
severity="blocker",
message=(
"Meta is an out-of-run support release, not a self-updating durable executor."
if core_ready else
"Prepare and commit Core at the requested target before regenerating Meta."
),
remediation=(
"Complete Core and module preparation first. Stop active durable runs for this workspace. "
"In a separate trusted source checkout, preview "
+ preparation_command(workspace=workspace, target_version=unit.target_version)
+ ". Review its receipt; apply with --receipt <preview.json> --apply --confirm-out-of-run. "
"Review and commit the whole generated package, publish the matching Core release first, "
"then use guarded Meta source tagging/publication and create a fresh durable run."
),
repo=unit.repo, source="developer meta-package preparation",
expected=unit.target_version, actual=core_version or "missing Core version",
)
)
version_update_supported_by_repo[unit.repo] = False
# Its complete canonical composition remains a publication gate;
# this plan must not claim a generic in-run mutation/commit path.
continue
version_update_supported = unit.current_version == unit.target_version
if unit.current_version and unit.current_version != unit.target_version:
try:
@@ -437,6 +471,7 @@ def build_unit(
value
for value in (
repo.versions.pyproject,
repo.versions.developer_meta,
repo.versions.package,
repo.versions.webui_package,
*repo.versions.manifests,
@@ -572,7 +607,7 @@ def repository_capabilities(
def dependency_ordered_units(
units: tuple[ReleasePlanUnit, ...],
) -> tuple[ReleasePlanUnit, ...]:
"""Order module providers before consumers while keeping Core last."""
"""Order modules before Core, followed by the out-of-run Meta support unit."""
by_repo = {unit.repo: unit for unit in units}
providers: dict[str, set[str]] = {}
@@ -592,8 +627,10 @@ def dependency_ordered_units(
)
if "govoplan-core" in dependencies:
dependencies["govoplan-core"].update(
repo for repo in by_repo if repo != "govoplan-core"
repo for repo in by_repo if repo not in {"govoplan-core", "govoplan"}
)
if "govoplan" in dependencies:
dependencies["govoplan"].update(repo for repo in by_repo if repo != "govoplan")
ordered: list[ReleasePlanUnit] = []
remaining = set(by_repo)
@@ -690,6 +727,8 @@ def dry_run_steps(
*, units: tuple[ReleasePlanUnit, ...], dashboard: ReleaseDashboard, channel: str
) -> tuple[ReleasePlanStep, ...]:
steps: list[ReleasePlanStep] = []
meta_units = tuple(unit for unit in units if unit.repo == "govoplan")
units = tuple(unit for unit in units if unit.repo != "govoplan")
snapshots = {repo.spec.name: repo for repo in dashboard.repositories}
core_unit = next((unit for unit in units if unit.repo == "govoplan-core"), None)
non_core_units = tuple(unit for unit in units if unit.repo != "govoplan-core")
@@ -991,6 +1030,33 @@ def dry_run_steps(
status="planned",
)
)
for unit in meta_units:
from .meta_preparation import preparation_command
steps.extend((
ReleasePlanStep(
id="govoplan:prepare-support",
title="Prepare the complete developer meta-package outside this run",
detail=(
"First prepare and commit Core at the target and review module/requirements inputs. "
"Stop active runs, preview and explicitly apply the frozen composition in a separate "
"source checkout; review and commit manually. No durable self-update is supported."
),
command=preparation_command(workspace=Path(dashboard.workspace_root), target_version=unit.target_version),
cwd=dashboard.meta_root, repo=unit.repo, status="needs-executor",
),
ReleasePlanStep(
id="govoplan:publish-support",
title="Publish the prepared Meta support source after Core",
detail=(
"After the matching Core annotated tag and exact main are published, use the shared "
"guarded Meta tag preview/local-tag/publish route. Commit/push reviewed preparation "
"and create a fresh durable run; do not update the current runtime binding."
),
cwd=dashboard.meta_root, repo=unit.repo, status="needs-executor",
mutating=True,
),
))
return tuple(steps)