Harden source release preparation and record verified security follow-up
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user