Validate complete Core release bundle sources
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 14s
Security Audit / security-audit (push) Failing after 13s

This commit is contained in:
2026-07-22 22:10:35 +02:00
parent b00d4e55ee
commit dc46bda224
2 changed files with 253 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from pathlib import Path from pathlib import Path
import os import os
import subprocess import subprocess
@@ -762,6 +763,141 @@ class ReleaseExecutionTests(unittest.TestCase):
_git_output(cloned["govoplan-core"], "rev-parse", "--abbrev-ref", "HEAD"), _git_output(cloned["govoplan-core"], "rev-parse", "--abbrev-ref", "HEAD"),
) )
def test_catalog_synthesis_adds_exact_core_bundle_dependency(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)
idm_repo, idm_remote = _tagged_repository(
workspace,
repo="govoplan-idm",
bare="idm.git",
version="0.1.8",
)
idm_commit = _git_output(idm_repo, "rev-parse", "v0.1.8^{commit}")
(idm_repo / "pyproject.toml").write_text(
'[project]\nname = "govoplan-idm"\nversion = "0.1.9"\n',
encoding="utf-8",
)
_git(idm_repo, "add", "pyproject.toml")
_git(idm_repo, "commit", "-m", "Next release")
_git(idm_repo, "tag", "-a", "v0.1.9", "-m", "Next release")
_git(idm_repo, "push", "origin", "main", "refs/tags/v0.1.9")
catalog_commit = _git_output(idm_repo, "rev-parse", "v0.1.9^{commit}")
core_repo = workspace / "govoplan-core"
core_remote = workspace / "core.git"
_git(workspace, "init", "--bare", str(core_remote))
_git(workspace, "init", "-b", "main", str(core_repo))
_git(core_repo, "config", "user.name", "Release Test")
_git(core_repo, "config", "user.email", "release@example.test")
(core_repo / "webui").mkdir()
(core_repo / "pyproject.toml").write_text(
'[project]\nname = "govoplan-core"\nversion = "1.0.0"\n',
encoding="utf-8",
)
reference = (
"git+ssh://git@example.test/add-ideas/"
"govoplan-idm.git#v0.1.8"
)
(core_repo / "webui/package.release.json").write_text(
json.dumps(
{
"version": "1.0.0",
"dependencies": {"@govoplan/idm-webui": reference},
}
),
encoding="utf-8",
)
(core_repo / "webui/package-lock.release.json").write_text(
json.dumps(
{
"packages": {
"": {
"version": "1.0.0",
"dependencies": {
"@govoplan/idm-webui": reference,
},
},
"node_modules/@govoplan/idm-webui": {
"version": "0.1.8",
"resolved": f"{reference.rsplit('#', 1)[0]}#{idm_commit}",
},
}
}
),
encoding="utf-8",
)
_git(core_repo, "add", ".")
_git(core_repo, "commit", "-m", "Release")
_git(core_repo, "tag", "-a", "v1.0.0", "-m", "Release")
_git(core_repo, "remote", "add", "origin", str(core_remote))
_git(core_repo, "push", "-u", "origin", "main", "refs/tags/v1.0.0")
specs = (
RepositorySpec(
name="govoplan-core",
category="system",
subtype="kernel",
remote=str(core_remote),
path="govoplan-core",
),
RepositorySpec(
name="govoplan-idm",
category="module",
subtype="platform",
remote=str(idm_remote),
path="govoplan-idm",
),
)
with patch(
"govoplan_release.release_execution.load_repository_specs",
return_value=specs,
):
synthesis = workspace / "synthesis"
synthesis.mkdir()
cloned = _clone_catalog_sources(
source_versions={"govoplan-core": "1.0.0"},
repo_versions={},
source_receipts={},
workspace_root=workspace,
destination=synthesis,
)
newer_synthesis = workspace / "newer-synthesis"
newer_synthesis.mkdir()
cloned_with_newer_catalog_source = _clone_catalog_sources(
source_versions={
"govoplan-core": "1.0.0",
"govoplan-idm": "0.1.9",
},
repo_versions={},
source_receipts={},
workspace_root=workspace,
destination=newer_synthesis,
)
self.assertEqual(
{"govoplan-core", "govoplan-idm"},
set(cloned),
)
self.assertEqual(
idm_commit,
_git_output(cloned["govoplan-idm"], "rev-parse", "HEAD"),
)
self.assertEqual(
catalog_commit,
_git_output(
cloned_with_newer_catalog_source["govoplan-idm"],
"rev-parse",
"HEAD",
),
)
self.assertEqual(
idm_commit,
_git_output(
cloned_with_newer_catalog_source["govoplan-idm"],
"rev-parse",
"v0.1.8^{commit}",
),
)
def _repository_spec(remote: Path) -> RepositorySpec: def _repository_spec(remote: Path) -> RepositorySpec:
return RepositorySpec( return RepositorySpec(

View File

@@ -7,6 +7,7 @@ import hashlib
import json import json
import os import os
from pathlib import Path from pathlib import Path
import re
import shutil import shutil
import signal import signal
import stat import stat
@@ -76,6 +77,9 @@ DURABLE_REMOTE = "origin"
FLATPAK_BUILDER_PROXY = Path("/usr/bin/flatpak-spawn") FLATPAK_BUILDER_PROXY = Path("/usr/bin/flatpak-spawn")
HOST_BUBBLEWRAP = Path("/usr/bin/bwrap") HOST_BUBBLEWRAP = Path("/usr/bin/bwrap")
FLATPAK_HOST_BUS = Path("/run/flatpak/bus") FLATPAK_HOST_BUS = Path("/run/flatpak/bus")
_WEBUI_RELEASE_SOURCE = re.compile(
r"/(?P<repo>govoplan-[a-z0-9-]+)\.git#v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)$"
)
class ReleaseExecutionError(RuntimeError): class ReleaseExecutionError(RuntimeError):
@@ -1631,9 +1635,122 @@ def _clone_catalog_sources(
tag=f"v{version.removeprefix('v')}", tag=f"v{version.removeprefix('v')}",
source_root=destination, source_root=destination,
) )
_clone_release_bundle_sources(
result=result,
specs=specs,
source_root=destination,
)
return result return result
def _clone_release_bundle_sources(
*,
result: dict[str, Path],
specs: dict[str, Any],
source_root: Path,
) -> None:
"""Add exact Core bundle dependencies needed only for alignment checks."""
core = result.get("govoplan-core")
if core is None:
raise ReleaseExecutionBlocked(
"Authenticated catalog composition has no Core source."
)
package_path = core / "webui" / "package.release.json"
lock_path = core / "webui" / "package-lock.release.json"
if not package_path.exists() and not lock_path.exists():
return
package = _read_bounded_release_json(
package_path,
label="Core release WebUI package",
)
lock = _read_bounded_release_json(
lock_path,
label="Core release WebUI lock",
)
dependencies = package.get("dependencies")
lock_packages = lock.get("packages")
if not isinstance(dependencies, dict) or not isinstance(lock_packages, dict):
raise ReleaseExecutionBlocked(
"Core release WebUI dependency metadata is incomplete."
)
if len(dependencies) > MAX_CATALOG_SOURCE_REPOSITORIES:
raise ReleaseExecutionBlocked(
"Core release WebUI dependency set exceeds its bound."
)
for package_name, reference in sorted(dependencies.items()):
if not isinstance(package_name, str) or not package_name.startswith(
"@govoplan/"
):
continue
match = (
_WEBUI_RELEASE_SOURCE.search(reference)
if isinstance(reference, str)
else None
)
if match is None:
raise ReleaseExecutionBlocked(
f"Core release dependency {package_name} has no immutable source tag."
)
repo = match.group("repo")
version = match.group("version")
repository = specs.get(repo)
if repository is None:
raise ReleaseExecutionBlocked(
f"Core release dependency {repo} is not registered."
)
locked = lock_packages.get(f"node_modules/{package_name}")
resolved = locked.get("resolved") if isinstance(locked, dict) else None
locked_version = locked.get("version") if isinstance(locked, dict) else None
commit = (
resolved.rsplit("#", 1)[-1]
if isinstance(resolved, str) and "#" in resolved
else ""
)
if (
locked_version != version
or not re.fullmatch(r"[0-9a-f]{40}|[0-9a-f]{64}", commit)
):
raise ReleaseExecutionBlocked(
f"Core release dependency {repo} has no exact lock identity."
)
if repo not in result:
if len(result) >= MAX_CATALOG_SOURCE_REPOSITORIES:
raise ReleaseExecutionBlocked(
"Release composition source set exceeds its bound."
)
result[repo] = _checkout_registered_source_tag(
repo=repo,
source_remote=repository.remote,
tag=f"v{version}",
source_root=source_root,
)
observed_type = git_text(
result[repo], "cat-file", "-t", f"refs/tags/v{version}"
)
observed = ref_commit(result[repo], f"refs/tags/v{version}")
if observed_type != "tag" or observed != commit:
raise ReleaseExecutionBlocked(
f"Core release dependency {repo} lock does not match its origin tag."
)
def _read_bounded_release_json(path: Path, *, label: str) -> dict[str, Any]:
try:
encoded = path.read_bytes()
except OSError as exc:
raise ReleaseExecutionBlocked(f"{label} cannot be read.") from exc
if len(encoded) > 4 * 1024 * 1024:
raise ReleaseExecutionBlocked(f"{label} exceeds its size limit.")
try:
payload = json.loads(encoded)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseExecutionBlocked(f"{label} is malformed.") from exc
if not isinstance(payload, dict):
raise ReleaseExecutionBlocked(f"{label} must be a JSON object.")
return payload
def verify_frozen_repository_tag_receipt( def verify_frozen_repository_tag_receipt(
*, receipt: dict[str, Any] | None, workspace_root: Path *, receipt: dict[str, Any] | None, workspace_root: Path
) -> dict[str, Any]: ) -> dict[str, Any]: