Files
govoplan/tools/release/govoplan_release/release_execution.py

2301 lines
80 KiB
Python

"""Narrow durable-run adapters for existing confirmed release executors."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import shutil
import signal
import stat
import subprocess
import tempfile
import tomllib
from typing import Any
from .artifact_identity import (
MAX_WHEEL_BYTES,
inspect_python_wheel,
normalize_package_name,
selected_artifact_identity_issues,
)
from .catalog import DEFAULT_PUBLIC_BASE_URL, canonical_hash
from .candidate_artifact import (
CandidateArtifactReceipt,
candidate_output_path,
harden_private_candidate_tree,
issue_candidate_receipt,
validate_release_channel,
)
from .git_state import (
collect_repository_snapshot,
git,
git_text,
sanitized_git_environment,
)
from .model import to_jsonable
from .module_directory import module_directory_payloads
from .publisher import (
bind_publication_remote,
catalog_public_base_url,
default_tag_name,
publication_runtime_trust_issues,
publish_catalog_candidate,
registered_website_remote,
remote_publication_identity,
validate_catalog_payload,
verify_committed_publication,
)
from .repository_tag import ref_commit, remote_tag_commit, tag_repositories
from .selective_catalog import (
build_selective_catalog_candidate,
configured_signer_public_keys,
load_authenticated_catalog_base,
parse_signing_key,
trusted_keys_from_keyring,
)
from .source_provenance import catalog_source_selection
from .workspace import (
META_ROOT,
REPOSITORIES_FILE,
load_repository_specs,
resolve_repo_path,
website_root,
)
MAX_EXECUTOR_OUTPUT = 4_000
MAX_SIGNING_KEYS = 8
MAX_REMOTE_METADATA_BYTES = 16 * 1024
MAX_CATALOG_SOURCE_REPOSITORIES = 128
MAX_TRUSTED_RUNTIME_ENTRIES = 10_000
MAX_TRUSTED_GIT_ENTRIES = 500_000
DURABLE_REMOTE = "origin"
class ReleaseExecutionError(RuntimeError):
"""Base error for a plan step that cannot be executed safely."""
class ReleaseExecutionBlocked(ReleaseExecutionError):
"""The executor proved that no intended external effect was completed."""
class ReleaseExecutionAmbiguous(ReleaseExecutionError):
"""The executor may have produced an effect and requires reconciliation."""
@dataclass(frozen=True, slots=True)
class ExecutorSpec:
kind: str
confirmation: str
result_code: str
mutating: bool
EXECUTOR_SPECS = {
"preflight": ExecutorSpec("preflight", "", "preflight_valid", False),
"tag": ExecutorSpec("tag", "TAG", "tag_created", True),
"push": ExecutorSpec("push", "PUBLISH", "tag_published", True),
"catalog:selective-generator": ExecutorSpec(
"catalog_generate", "GENERATE", "catalog_candidate_ready", True
),
"catalog:validate-sign-publish": ExecutorSpec(
"catalog_publish", "PUSH", "catalog_published", True
),
}
def executor_spec(plan_step: dict[str, Any]) -> ExecutorSpec | None:
step_id = plan_step.get("id")
if step_id in EXECUTOR_SPECS:
spec = EXECUTOR_SPECS[step_id]
elif isinstance(step_id, str) and ":" in step_id:
spec = EXECUTOR_SPECS.get(step_id.rsplit(":", 1)[1])
else:
spec = None
if spec is None or plan_step.get("status") != "planned":
return None
if plan_step.get("mutating") is not spec.mutating:
return None
if spec.kind == "catalog_generate":
return spec if plan_step.get("repo") is None else None
if spec.kind == "catalog_publish":
binding = plan_step.get("source_binding")
return (
spec
if plan_step.get("repo") is None
and isinstance(binding, dict)
and binding.get("repo") == "addideas-govoplan-website"
else None
)
repo = plan_step.get("repo")
binding = plan_step.get("source_binding")
return (
spec
if isinstance(repo, str)
and repo
and isinstance(binding, dict)
and binding.get("kind") == "repository_state"
else None
)
def require_trusted_release_runtime(*, workspace_root: Path) -> None:
"""Require an operator-controlled console/config and workspace boundary."""
dependency_issues = publication_runtime_trust_issues()
if dependency_issues:
raise ReleaseExecutionBlocked(
"Release runtime is not operator-controlled: "
+ "; ".join(dependency_issues)
)
_require_operator_checkout(META_ROOT)
_require_trusted_tree(META_ROOT / "tools" / "release")
_require_trusted_tree(META_ROOT / "tools" / "checks")
_require_trusted_regular_file(REPOSITORIES_FILE, label="repository registry")
migration_baseline = META_ROOT / "docs" / "migration-release-baselines.json"
if migration_baseline.exists():
_require_trusted_regular_file(
migration_baseline,
label="migration release baseline",
)
_require_trusted_directory(workspace_root, label="release workspace")
_require_trusted_ancestor_chain(workspace_root)
def bind_plan_source_states(
*,
plan: dict[str, Any],
repo_versions: dict[str, str],
workspace_root: Path,
) -> dict[str, Any]:
"""Freeze creation-time Git identities into the immutable plan snapshot."""
plan["runtime_binding"] = trusted_release_runtime_receipt(
workspace_root=workspace_root
)
steps = plan.get("dry_run_steps")
if not isinstance(steps, list):
raise ReleaseExecutionBlocked("Release plan steps are unavailable.")
bindings: dict[str, dict[str, Any] | None] = {}
for repo, version in repo_versions.items():
try:
bindings[repo] = repository_state_receipt(
repo=repo,
target_tag=f"v{version.removeprefix('v')}",
workspace_root=workspace_root,
)
except ReleaseExecutionBlocked:
bindings[repo] = None
try:
website_binding = repository_state_receipt(
repo="addideas-govoplan-website",
target_tag="",
workspace_root=workspace_root,
include_website=True,
)
except ReleaseExecutionBlocked:
website_binding = None
for step in steps:
if not isinstance(step, dict):
continue
repo = step.get("repo")
if isinstance(repo, str):
step["source_binding"] = bindings.get(repo)
elif step.get("id") == "catalog:validate-sign-publish":
step["source_binding"] = website_binding
return plan
def trusted_release_runtime_receipt(*, workspace_root: Path) -> dict[str, Any]:
"""Bind trusted release code/config to one clean origin branch commit."""
require_trusted_release_runtime(workspace_root=workspace_root)
specs = {item.name: item for item in load_repository_specs(include_website=False)}
meta_spec = specs.get("govoplan")
if meta_spec is None or resolve_repo_path(meta_spec, workspace_root) != META_ROOT:
raise ReleaseExecutionBlocked(
"Trusted release tooling is not the registered workspace meta repository."
)
receipt = repository_state_receipt(
repo="govoplan",
target_tag="",
workspace_root=workspace_root,
)
if not receipt["worktree_clean"]:
raise ReleaseExecutionBlocked(
"Release tooling checkout must be clean before a durable run."
)
path = resolve_repo_path(meta_spec, workspace_root)
remote_head = _remote_branch_commit(path, branch=str(receipt["branch"]))
if remote_head != receipt["head"]:
raise ReleaseExecutionBlocked(
"Release tooling HEAD must equal its registered origin branch."
)
return receipt
def verify_release_runtime_binding(
*, expected: object, workspace_root: Path
) -> dict[str, Any]:
current = trusted_release_runtime_receipt(workspace_root=workspace_root)
_require_receipt_match(current, expected, operation="release execution")
return current
def execute_repository_step(
*,
spec: ExecutorSpec,
plan_step: dict[str, Any],
repo_versions: dict[str, str],
workspace_root: Path,
remote: str,
expected_receipt: dict[str, Any] | None,
) -> tuple[dict[str, Any], dict[str, Any]]:
require_trusted_release_runtime(workspace_root=workspace_root)
if remote != DURABLE_REMOTE:
raise ReleaseExecutionBlocked("Durable release execution uses only origin.")
repo = str(plan_step["repo"])
version = repo_versions.get(repo)
if version is None:
raise ReleaseExecutionBlocked(
"The frozen plan step has no matching repository version."
)
if spec.kind == "preflight":
result, receipt = repository_preflight(
repo=repo,
version=version,
workspace_root=workspace_root,
)
_require_receipt_match(
receipt,
plan_step.get("source_binding"),
operation="repository preflight",
)
return result, receipt
verify_repository_step_precondition(
spec=spec,
plan_step=plan_step,
version=version,
workspace_root=workspace_root,
expected_receipt=expected_receipt,
)
if spec.kind == "tag":
result = tag_repositories(
repos=(repo,),
repo_versions={repo: version},
workspace_root=workspace_root,
remote=remote,
apply=True,
push=False,
)
payload = _known_or_ambiguous_result(
result,
success={"tagged", "noop"},
operation="release tag creation",
)
try:
receipt = repository_state_receipt(
repo=repo,
target_tag=f"v{version.removeprefix('v')}",
workspace_root=workspace_root,
)
if receipt["tag_object"] is None:
raise ReleaseExecutionBlocked(
"Tag executor returned success without a verifiable annotated "
"tag object."
)
_require_same_repository_base(receipt, expected_receipt)
except ReleaseExecutionBlocked as exc:
raise ReleaseExecutionAmbiguous(
"The tag executor reported success, but its post-effect repository "
"receipt could not be verified; reconcile the local tag."
) from exc
return payload, receipt
if spec.kind == "push":
result = tag_repositories(
repos=(repo,),
repo_versions={repo: version},
workspace_root=workspace_root,
remote=remote,
apply=True,
push=True,
)
payload = _known_or_ambiguous_result(
result,
success={"published"},
operation="atomic release tag publication",
)
try:
receipt = verified_repository_publication_receipt(
repo=repo,
version=version,
workspace_root=workspace_root,
expected_receipt=expected_receipt,
)
except ReleaseExecutionBlocked as exc:
raise ReleaseExecutionAmbiguous(
"The push executor reported success, but its post-effect remote "
"receipt could not be verified; reconcile the local and remote tag."
) from exc
return payload, receipt
raise ReleaseExecutionBlocked("No repository executor matches this plan step.")
def repository_preflight(
*, repo: str, version: str, workspace_root: Path
) -> tuple[dict[str, Any], dict[str, Any]]:
specs = {item.name: item for item in load_repository_specs(include_website=False)}
repository = specs.get(repo)
if repository is None:
raise ReleaseExecutionBlocked("Repository is not registered.")
path = resolve_repo_path(repository, workspace_root)
_require_operator_checkout(path)
snapshot = collect_repository_snapshot(
repository,
workspace_root=workspace_root,
target_tag=f"v{version.removeprefix('v')}",
online=False,
)
if not snapshot.exists or not snapshot.is_git:
raise ReleaseExecutionBlocked("Repository is missing or is not a Git checkout.")
if snapshot.safe_directory_required:
raise ReleaseExecutionBlocked("Git safe.directory approval is required.")
if not snapshot.has_head or not snapshot.branch:
raise ReleaseExecutionBlocked("Repository has no named-branch HEAD.")
tag = f"v{version.removeprefix('v')}"
receipt = repository_state_receipt(
repo=repo,
target_tag=tag,
workspace_root=workspace_root,
)
return {
"status": "inspected",
"repo": repo,
"head": receipt["head"],
"branch": receipt["branch"],
"ahead": int(snapshot.ahead or 0),
"behind": int(snapshot.behind or 0),
"dirty_count": len(snapshot.dirty_entries),
"target_tag": tag,
"local_tag_commit": ref_commit(
resolve_repo_path(repository, workspace_root), f"refs/tags/{tag}"
),
}, receipt
def repository_state_receipt(
*,
repo: str,
target_tag: str,
workspace_root: Path,
include_website: bool = False,
) -> dict[str, Any]:
"""Capture only bounded repository identity, never a credential-bearing URL."""
specs = {
item.name: item
for item in load_repository_specs(include_website=include_website)
}
repository = specs.get(repo)
if repository is None:
raise ReleaseExecutionBlocked("Repository is not registered.")
path = resolve_repo_path(repository, workspace_root)
_require_operator_checkout(path)
snapshot = collect_repository_snapshot(
repository,
workspace_root=workspace_root,
target_tag=target_tag or None,
online=False,
)
if not snapshot.exists or not snapshot.is_git:
raise ReleaseExecutionBlocked("Repository is missing or is not a Git checkout.")
if snapshot.safe_directory_required:
raise ReleaseExecutionBlocked("Git safe.directory approval is required.")
if not snapshot.has_head or not snapshot.branch:
raise ReleaseExecutionBlocked("Repository has no named-branch HEAD.")
head = git_text(path, "rev-parse", "--verify", "HEAD")
if len(head) not in {40, 64}:
raise ReleaseExecutionBlocked("Repository HEAD cannot be bound safely.")
fetch_urls = _bounded_remote_urls(path, push=False)
push_urls = _bounded_remote_urls(path, push=True)
expected_remote = repository.remote.strip()
if fetch_urls != (expected_remote,) or push_urls != (expected_remote,):
raise ReleaseExecutionBlocked(
"Repository origin does not match its registered release remote."
)
tag_object = (
git_text(path, "rev-parse", "--verify", f"refs/tags/{target_tag}")
if target_tag
else None
) or None
if tag_object is not None and len(tag_object) not in {40, 64}:
raise ReleaseExecutionBlocked("Repository tag object cannot be bound safely.")
if tag_object is not None and git_text(
path, "cat-file", "-t", f"refs/tags/{target_tag}"
) != "tag":
raise ReleaseExecutionBlocked(
"Existing release tag is not an annotated tag object."
)
return {
"kind": "repository_state",
"repo": repo,
"head": head,
"branch": snapshot.branch,
"remote": DURABLE_REMOTE,
"remote_sha256": hashlib.sha256(
json.dumps(
{"fetch": fetch_urls, "push": push_urls},
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
).encode("utf-8")
).hexdigest(),
"worktree_clean": not snapshot.dirty_entries,
"target_tag": target_tag,
"tag_object": tag_object,
}
def _require_operator_checkout(path: Path) -> None:
_require_trusted_ancestor_chain(path)
required = (
(path, "repository", True),
(path / ".git", "Git metadata", True),
(path / ".git" / "config", "Git configuration", False),
(path / ".git" / "HEAD", "Git HEAD", False),
(path / ".git" / "objects", "Git object database", True),
(path / ".git" / "refs", "Git references", True),
)
optional = (
(path / ".git" / "index", "Git index", False),
(path / ".git" / "packed-refs", "packed Git references", False),
)
for candidate, label, directory in (*required, *optional):
try:
metadata = candidate.lstat()
except FileNotFoundError:
if (candidate, label, directory) in optional:
continue
raise ReleaseExecutionBlocked(
f"Release {label} cannot be inspected safely."
) from None
except OSError as exc:
raise ReleaseExecutionBlocked(
f"Release {label} cannot be inspected safely."
) from exc
expected_type = stat.S_ISDIR if directory else stat.S_ISREG
if stat.S_ISLNK(metadata.st_mode) or not expected_type(metadata.st_mode):
raise ReleaseExecutionBlocked(
f"Release {label} must be a real operator-controlled path."
)
if hasattr(os, "geteuid") and metadata.st_uid != os.geteuid():
raise ReleaseExecutionBlocked(
f"Release {label} is not owned by the console operator."
)
if metadata.st_mode & 0o022:
raise ReleaseExecutionBlocked(
f"Release {label} is group/world writable."
)
_require_trusted_git_tree(path / ".git" / "refs")
_require_trusted_git_tree(path / ".git" / "objects")
if any(
candidate.exists()
for candidate in (
path / ".git" / "objects" / "info" / "alternates",
path / ".git" / "info" / "grafts",
)
):
raise ReleaseExecutionBlocked(
"Release Git object alternates and grafts are not permitted."
)
_require_trusted_tracked_worktree(path)
def _require_trusted_git_tree(root: Path) -> None:
"""Boundedly inspect nested authority-bearing Git metadata."""
observed = 0
pending = [root]
while pending:
parent = pending.pop()
_require_trusted_directory(parent, label="Git metadata directory")
try:
entries = tuple(os.scandir(parent))
except OSError as exc:
raise ReleaseExecutionBlocked(
"Nested Git metadata cannot be inspected safely."
) from exc
for entry in entries:
observed += 1
if observed > MAX_TRUSTED_GIT_ENTRIES:
raise ReleaseExecutionBlocked(
"Nested Git metadata exceeds its trust-validation limit."
)
candidate = Path(entry.path)
try:
metadata = candidate.lstat()
except OSError as exc:
raise ReleaseExecutionBlocked(
"Nested Git metadata changed during trust validation."
) from exc
if stat.S_ISDIR(metadata.st_mode):
pending.append(candidate)
elif stat.S_ISREG(metadata.st_mode):
_require_trusted_regular_file(
candidate,
label="Git metadata file",
)
else:
raise ReleaseExecutionBlocked(
"Nested Git metadata contains a symlink or special file."
)
def _require_trusted_tracked_worktree(path: Path) -> None:
"""Reject tracked metadata inputs that another local user can race."""
result = git(path, "ls-files", "-z", timeout=30)
encoded = result.stdout.encode("utf-8", errors="strict")
if result.returncode != 0 or len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(
"Tracked release worktree paths cannot be inspected safely."
)
names = [name for name in result.stdout.split("\0") if name]
if len(names) > 100_000:
raise ReleaseExecutionBlocked(
"Tracked release worktree exceeds its trust-validation limit."
)
checked_directories: set[Path] = {path}
for name in names:
relative = Path(name)
if (
relative.is_absolute()
or not relative.parts
or any(part in {"", ".", ".."} for part in relative.parts)
):
raise ReleaseExecutionBlocked(
"Tracked release worktree contains a non-canonical path."
)
candidate = path / relative
pending_directories: list[Path] = []
parent = candidate.parent
while parent != path:
if path not in parent.parents:
raise ReleaseExecutionBlocked(
"Tracked release worktree path leaves its repository."
)
if parent not in checked_directories:
pending_directories.append(parent)
parent = parent.parent
for directory in reversed(pending_directories):
_require_trusted_directory(
directory,
label="tracked repository directory",
)
checked_directories.add(directory)
try:
metadata = candidate.lstat()
except OSError as exc:
raise ReleaseExecutionBlocked(
"Tracked release worktree changed during trust validation."
) from exc
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISREG(metadata.st_mode)
or metadata.st_uid != os.geteuid()
or metadata.st_mode & 0o022
):
raise ReleaseExecutionBlocked(
"Tracked release files must be operator-owned, non-writable regular files."
)
def _require_trusted_ancestor_chain(path: Path) -> None:
"""Reject replaceable path ancestors; root/current owners are authorities."""
resolved = path.absolute()
for ancestor in resolved.parents:
try:
metadata = ancestor.lstat()
except OSError as exc:
raise ReleaseExecutionBlocked(
"Release path ancestry cannot be inspected safely."
) from exc
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
raise ReleaseExecutionBlocked(
"Release path ancestry must contain only real directories."
)
if hasattr(os, "geteuid") and metadata.st_uid not in {0, os.geteuid()}:
raise ReleaseExecutionBlocked(
"Release path ancestry is not owned by root or the console operator."
)
if metadata.st_mode & 0o022:
raise ReleaseExecutionBlocked(
"Release path ancestry is group/world writable."
)
def _require_trusted_directory(path: Path, *, label: str) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise ReleaseExecutionBlocked(f"{label.capitalize()} is unavailable.") from exc
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISDIR(metadata.st_mode)
or (hasattr(os, "geteuid") and metadata.st_uid != os.geteuid())
or metadata.st_mode & 0o022
):
raise ReleaseExecutionBlocked(
f"{label.capitalize()} must be an operator-owned, non-writable real directory."
)
def _require_trusted_regular_file(path: Path, *, label: str) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise ReleaseExecutionBlocked(f"{label.capitalize()} is unavailable.") from exc
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISREG(metadata.st_mode)
or (hasattr(os, "geteuid") and metadata.st_uid != os.geteuid())
or metadata.st_mode & 0o022
):
raise ReleaseExecutionBlocked(
f"{label.capitalize()} must be an operator-owned, non-writable regular file."
)
def _require_trusted_tree(root: Path) -> None:
_require_trusted_directory(root, label="release tooling directory")
observed = 0
for current, directory_names, file_names in os.walk(root, followlinks=False):
current_path = Path(current)
_require_trusted_directory(current_path, label="release tooling directory")
for name in (*directory_names, *file_names):
observed += 1
if observed > MAX_TRUSTED_RUNTIME_ENTRIES:
raise ReleaseExecutionBlocked(
"Release tooling tree exceeds its trust-validation limit."
)
child = current_path / name
metadata = child.lstat()
if stat.S_ISDIR(metadata.st_mode):
_require_trusted_directory(child, label="release tooling directory")
elif stat.S_ISREG(metadata.st_mode):
_require_trusted_regular_file(child, label="release tooling file")
else:
raise ReleaseExecutionBlocked(
"Release tooling tree contains a symlink or non-file member."
)
def _bounded_remote_urls(path: Path, *, push: bool) -> tuple[str, ...]:
arguments = ["remote", "get-url", "--all"]
if push:
arguments.append("--push")
arguments.append(DURABLE_REMOTE)
result = git(path, *arguments)
encoded = result.stdout.encode("utf-8", errors="strict")
lines = tuple(line.strip() for line in result.stdout.splitlines() if line.strip())
if (
result.returncode != 0
or not lines
or len(lines) > 16
or len(encoded) > MAX_REMOTE_METADATA_BYTES
):
kind = "push" if push else "fetch"
raise ReleaseExecutionBlocked(
f"Repository origin {kind} URLs cannot be bound safely."
)
return lines
def _remote_branch_commit(path: Path, *, branch: str) -> str | None:
result = git(
path,
"ls-remote",
"--heads",
DURABLE_REMOTE,
f"refs/heads/{branch}",
timeout=15,
)
rows = [line.split("\t", 1) for line in result.stdout.splitlines()]
expected_ref = f"refs/heads/{branch}"
if (
result.returncode != 0
or len(rows) != 1
or len(rows[0]) != 2
or rows[0][1] != expected_ref
or len(rows[0][0]) not in {40, 64}
):
return None
return rows[0][0]
def verify_repository_step_precondition(
*,
spec: ExecutorSpec,
plan_step: dict[str, Any],
version: str,
workspace_root: Path,
expected_receipt: dict[str, Any] | None,
) -> dict[str, Any]:
if spec.kind not in {"tag", "push"}:
raise ReleaseExecutionBlocked("Repository precondition is unsupported.")
if not isinstance(expected_receipt, dict):
raise ReleaseExecutionBlocked(
"Repository mutation requires the preceding durable state receipt."
)
current = repository_state_receipt(
repo=str(plan_step["repo"]),
target_tag=f"v{version.removeprefix('v')}",
workspace_root=workspace_root,
)
_require_receipt_match(current, expected_receipt, operation=spec.kind)
if not current["worktree_clean"]:
raise ReleaseExecutionBlocked(
"Repository worktree changed after the run was frozen; create a new run."
)
if spec.kind == "push" and current["tag_object"] is None:
raise ReleaseExecutionBlocked("Tag publication requires an annotated local tag.")
return current
def verify_repository_preflight_binding(
*, plan_step: dict[str, Any], version: str, workspace_root: Path
) -> dict[str, Any]:
current = repository_state_receipt(
repo=str(plan_step["repo"]),
target_tag=f"v{version.removeprefix('v')}",
workspace_root=workspace_root,
)
_require_receipt_match(
current,
plan_step.get("source_binding"),
operation="repository preflight",
)
return current
def verify_catalog_publication_precondition(
*, plan_step: dict[str, Any], workspace_root: Path
) -> dict[str, Any]:
current = repository_state_receipt(
repo="addideas-govoplan-website",
target_tag="",
workspace_root=workspace_root,
include_website=True,
)
_require_receipt_match(
current,
plan_step.get("source_binding"),
operation="catalog publication",
)
if not current["worktree_clean"]:
raise ReleaseExecutionBlocked(
"Website worktree changed after the run was frozen; create a new run."
)
return current
def reconciled_repository_receipt(
*,
spec: ExecutorSpec,
plan_step: dict[str, Any],
version: str,
workspace_root: Path,
expected_receipt: dict[str, Any] | None,
) -> dict[str, Any]:
"""Independently prove the repository effect before advancing reconciliation."""
if spec.kind == "tag":
if not isinstance(expected_receipt, dict):
raise ReleaseExecutionBlocked(
"Tag reconciliation requires the preceding repository receipt."
)
receipt = repository_state_receipt(
repo=str(plan_step["repo"]),
target_tag=f"v{version.removeprefix('v')}",
workspace_root=workspace_root,
)
_require_same_repository_base(receipt, expected_receipt)
if receipt["tag_object"] is None:
raise ReleaseExecutionBlocked(
"The annotated local tag effect could not be verified."
)
return receipt
if spec.kind == "push":
return verified_repository_publication_receipt(
repo=str(plan_step["repo"]),
version=version,
workspace_root=workspace_root,
expected_receipt=expected_receipt,
)
raise ReleaseExecutionBlocked(
"This repository effect has no automatic success reconciliation proof."
)
def verified_repository_publication_receipt(
*,
repo: str,
version: str,
workspace_root: Path,
expected_receipt: dict[str, Any] | None,
) -> dict[str, Any]:
if not isinstance(expected_receipt, dict):
raise ReleaseExecutionBlocked(
"Tag publication requires the preceding tag receipt."
)
tag = f"v{version.removeprefix('v')}"
receipt = repository_state_receipt(
repo=repo,
target_tag=tag,
workspace_root=workspace_root,
)
_require_receipt_match(receipt, expected_receipt, operation="tag publication")
specs = {item.name: item for item in load_repository_specs(include_website=False)}
path = resolve_repo_path(specs[repo], workspace_root)
remote = remote_tag_commit(path, remote=DURABLE_REMOTE, tag=tag)
if remote.error:
raise ReleaseExecutionBlocked(remote.error)
if (
not remote.annotated
or remote.commit != receipt["head"]
or remote.tag_object != receipt["tag_object"]
):
raise ReleaseExecutionBlocked(
"Remote annotated tag does not match the receipt-bound local tag."
)
if _remote_branch_commit(path, branch=str(receipt["branch"])) != receipt["head"]:
raise ReleaseExecutionBlocked(
"Remote branch does not match the receipt-bound atomic publication."
)
return receipt
def _require_receipt_match(
current: dict[str, Any], expected: object, *, operation: str
) -> None:
if current != expected:
raise ReleaseExecutionBlocked(
f"Repository state drifted before {operation}; create a new release run."
)
def _require_same_repository_base(
current: dict[str, Any], expected: dict[str, Any] | None
) -> None:
if not isinstance(expected, dict):
raise ReleaseExecutionBlocked("Preceding repository receipt is unavailable.")
ignored = {"tag_object"}
if any(
current.get(key) != value
for key, value in expected.items()
if key not in ignored
):
raise ReleaseExecutionBlocked(
"Repository identity changed while creating the release tag."
)
def generate_catalog_candidate(
*,
candidate_root: Path,
candidate_id: str,
channel: str,
repo_versions: dict[str, str],
workspace_root: Path,
signing_keys: tuple[str, ...],
remote: str,
check_public: bool,
source_receipts: dict[str, dict[str, Any]],
base_catalog: Path,
base_keyring: Path,
) -> tuple[dict[str, Any], CandidateArtifactReceipt]:
require_trusted_release_runtime(workspace_root=workspace_root)
if not signing_keys or len(signing_keys) > MAX_SIGNING_KEYS:
raise ReleaseExecutionBlocked(
"Durable catalog generation requires one to eight configured signing keys."
)
output = candidate_output_path(candidate_root, candidate_id)
if output.exists():
raise ReleaseExecutionAmbiguous(
"The server-issued candidate handle already exists; reconcile before reuse."
)
try:
frozen_sources = {
repo: verify_frozen_repository_tag_receipt(
receipt=source_receipts.get(repo),
workspace_root=workspace_root,
)
for repo in repo_versions
}
with tempfile.TemporaryDirectory(
prefix="govoplan-release-synthesis-",
dir=candidate_root,
) as synthesis_dir:
synthesis_workspace = Path(synthesis_dir)
authenticated_base, snapshot_catalog, snapshot_keyring = (
_snapshot_authenticated_catalog_base(
base_catalog=base_catalog,
base_keyring=base_keyring,
channel=channel,
signing_keys=signing_keys,
workspace_root=workspace_root,
snapshot_root=synthesis_workspace / ".authenticated-base",
)
)
base_selection = catalog_source_selection(authenticated_base)
source_versions = dict(base_selection.all_versions)
source_versions.update(repo_versions)
if (
not source_versions
or len(source_versions) > MAX_CATALOG_SOURCE_REPOSITORIES
):
raise ReleaseExecutionBlocked(
"Authenticated catalog has no bounded source repository set."
)
cloned_sources = _clone_catalog_sources(
source_versions=source_versions,
repo_versions=repo_versions,
source_receipts=frozen_sources,
workspace_root=workspace_root,
destination=synthesis_workspace,
)
python_artifacts = build_selected_wheels(
repo_versions=repo_versions,
workspace_root=synthesis_workspace,
candidate_output=output,
source_receipts=frozen_sources,
frozen_sources=cloned_sources,
)
candidate = build_selective_catalog_candidate(
repo_versions=repo_versions,
channel=channel,
workspace_root=synthesis_workspace,
output_dir=output,
signing_keys=signing_keys,
base_catalog=snapshot_catalog,
base_keyring=snapshot_keyring,
source_remote=remote,
check_public=check_public,
python_artifacts=python_artifacts,
frozen_source_provenance={
repo: {
"commit_sha": str(receipt["head"]),
"tag_object_sha": str(receipt["tag_object"]),
}
for repo, receipt in frozen_sources.items()
},
)
harden_private_candidate_tree(output)
payload = _read_generated_catalog(output, channel=channel)
identity_issues = selected_artifact_identity_issues(payload)
if identity_issues:
raise ReleaseExecutionBlocked(
"Generated catalog lacks selected release artifact identities: "
+ "; ".join(identity_issues)
)
if getattr(candidate, "status", None) != "ready":
raise ReleaseExecutionBlocked(
"Generated catalog candidate did not pass signature validation."
)
receipt = issue_candidate_receipt(
root=candidate_root,
candidate_id=candidate_id,
channel=channel,
)
return to_jsonable(candidate), receipt
except ReleaseExecutionAmbiguous:
raise
except ReleaseExecutionError:
_remove_partial_candidate(output)
raise
except (OSError, subprocess.SubprocessError, ValueError) as exc:
_remove_partial_candidate(output)
raise ReleaseExecutionBlocked(
f"Catalog candidate generation failed before a trusted receipt: {exc}"
) from exc
def validated_candidate_receipt(
*, candidate_root: Path, candidate_id: str, channel: str
) -> CandidateArtifactReceipt:
candidate = candidate_output_path(candidate_root, candidate_id)
payload = _read_generated_catalog(candidate, channel=channel)
issues = selected_artifact_identity_issues(payload)
if issues:
raise ReleaseExecutionBlocked(
"Candidate lacks selected release artifact identities: "
+ "; ".join(issues)
)
return issue_candidate_receipt(
root=candidate_root,
candidate_id=candidate_id,
channel=channel,
)
def _snapshot_authenticated_catalog_base(
*,
base_catalog: Path,
base_keyring: Path,
channel: str,
signing_keys: tuple[str, ...],
workspace_root: Path,
snapshot_root: Path,
) -> tuple[dict[str, Any], Path, Path]:
"""Authenticate once, then pin the exact JSON pair in private storage."""
parsed_keys = tuple(parse_signing_key(value) for value in signing_keys)
authenticated = load_authenticated_catalog_base(
base_catalog=base_catalog,
base_keyring=base_keyring,
web_root=website_root(workspace_root),
channel=channel,
public_base_url=DEFAULT_PUBLIC_BASE_URL,
signer_public_keys=configured_signer_public_keys(parsed_keys),
)
snapshot_root.mkdir(mode=0o700, parents=False, exist_ok=False)
os.chmod(snapshot_root, 0o700, follow_symlinks=False)
catalog_path = snapshot_root / f"{validate_release_channel(channel)}.json"
keyring_path = snapshot_root / "keyring.json"
_write_private_json_snapshot(catalog_path, authenticated.catalog)
_write_private_json_snapshot(keyring_path, authenticated.keyring)
return authenticated.catalog, catalog_path, keyring_path
def _write_private_json_snapshot(path: Path, payload: object) -> None:
encoded = (
json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=True) + "\n"
).encode("utf-8")
if len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(
"Authenticated catalog trust material exceeds its size limit."
)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, 0o600)
try:
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "wb", closefd=False) as handle:
handle.write(encoded)
handle.flush()
os.fsync(handle.fileno())
finally:
os.close(descriptor)
def build_selected_wheels(
*,
repo_versions: dict[str, str],
workspace_root: Path,
candidate_output: Path,
source_receipts: dict[str, dict[str, Any]],
frozen_sources: dict[str, Path] | None = None,
) -> dict[str, Path]:
artifacts = candidate_output / "artifacts"
artifacts.mkdir(parents=True, exist_ok=False)
specs = {item.name: item for item in load_repository_specs(include_website=False)}
if frozen_sources is None:
with tempfile.TemporaryDirectory(
prefix="govoplan-release-build-"
) as temp_dir:
cloned = _clone_frozen_sources(
repo_versions=repo_versions,
source_receipts=source_receipts,
workspace_root=workspace_root,
destination=Path(temp_dir),
)
return _build_selected_wheels_from_sources(
repo_versions=repo_versions,
candidate_output=candidate_output,
source_receipts=source_receipts,
frozen_sources=cloned,
specs=specs,
workspace_root=workspace_root,
)
return _build_selected_wheels_from_sources(
repo_versions=repo_versions,
candidate_output=candidate_output,
source_receipts=source_receipts,
frozen_sources=frozen_sources,
specs=specs,
workspace_root=workspace_root,
)
def _build_selected_wheels_from_sources(
*,
repo_versions: dict[str, str],
candidate_output: Path,
source_receipts: dict[str, dict[str, Any]],
frozen_sources: dict[str, Path],
specs: dict[str, Any],
workspace_root: Path,
) -> dict[str, Path]:
artifacts = candidate_output / "artifacts"
if not artifacts.exists():
artifacts.mkdir(parents=True, exist_ok=False)
result: dict[str, Path] = {}
for repo in sorted(repo_versions):
repository = specs.get(repo)
receipt = source_receipts.get(repo)
frozen_path = frozen_sources.get(repo)
if (
repository is None
or not isinstance(receipt, dict)
or frozen_path is None
):
raise ReleaseExecutionBlocked(
f"Repository {repo} has no verified frozen source receipt."
)
project_name = python_project_name(frozen_path)
if project_name is None:
continue
staging = Path(
tempfile.mkdtemp(prefix=f".{repo}-build-", dir=artifacts)
)
os.chmod(staging, 0o700, follow_symlinks=False)
command = isolated_wheel_builder_command(
source=frozen_path,
output=staging,
)
if _run_isolated_wheel_builder(command) != 0:
raise ReleaseExecutionBlocked(
f"Server-owned wheel staging failed for {repo}: "
"the isolated no-network worker did not complete successfully"
)
created = _single_isolated_wheel(staging)
destination = artifacts / created.name
if destination.exists() or destination.is_symlink():
raise ReleaseExecutionBlocked(
f"Isolated wheel output name collides for {repo}."
)
_copy_isolated_wheel(created, destination)
identity = inspect_python_wheel(destination)
if (
identity.package_name != normalize_package_name(project_name)
or identity.package_version != repo_versions[repo].removeprefix("v")
):
destination.unlink(missing_ok=True)
raise ReleaseExecutionBlocked(
f"Isolated wheel identity does not match selected source {repo}."
)
os.chmod(destination, 0o600, follow_symlinks=False)
created.unlink()
staging.rmdir()
result[repo] = destination
return result
def isolated_wheel_builder_command(*, source: Path, output: Path) -> tuple[str, ...]:
"""Build one absolute bubblewrap command with no host data or network view."""
launcher = isolated_builder_launcher()
return (
*launcher,
"--unshare-all",
"--die-with-parent",
"--new-session",
"--cap-drop",
"ALL",
"--ro-bind",
"/usr",
"/usr",
"--symlink",
"usr/bin",
"/bin",
"--symlink",
"usr/lib",
"/lib",
"--symlink",
"usr/lib64",
"/lib64",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--dir",
"/tmp/home",
"--ro-bind",
str(source.absolute()),
"/src",
"--bind",
str(output.absolute()),
"/out",
"--chdir",
"/tmp",
"--clearenv",
"--setenv",
"PATH",
"/usr/bin",
"--setenv",
"HOME",
"/tmp/home",
"--setenv",
"TMPDIR",
"/tmp",
"--setenv",
"PYTHONNOUSERSITE",
"1",
"--setenv",
"PYTHONHASHSEED",
"0",
"--setenv",
"SOURCE_DATE_EPOCH",
"315532800",
"--setenv",
"PIP_CONFIG_FILE",
"/dev/null",
"--setenv",
"PIP_DISABLE_PIP_VERSION_CHECK",
"1",
"--setenv",
"PIP_NO_CACHE_DIR",
"1",
"--setenv",
"PIP_NO_INDEX",
"1",
"/usr/bin/prlimit",
f"--fsize={MAX_WHEEL_BYTES}",
"--cpu=600",
"--nproc=256",
"--as=4294967296",
"--",
"/usr/bin/python3",
"-m",
"pip",
"wheel",
"--no-deps",
"--no-build-isolation",
"--disable-pip-version-check",
"--no-index",
"--wheel-dir",
"/out",
"/src",
)
def isolated_builder_launcher() -> tuple[str, ...]:
flatpak_spawn = Path("/usr/bin/flatpak-spawn")
direct_bwrap = Path("/usr/bin/bwrap")
if _trusted_system_executable(flatpak_spawn):
return (str(flatpak_spawn), "--host", "/usr/bin/bwrap")
if _trusted_system_executable(direct_bwrap):
return (str(direct_bwrap),)
raise ReleaseExecutionBlocked(
"Catalog generation requires a trusted bubblewrap worker; no supported "
"isolated builder is available."
)
def _trusted_system_executable(path: Path) -> bool:
try:
metadata = path.lstat()
except OSError:
return False
return (
stat.S_ISREG(metadata.st_mode)
and metadata.st_uid == 0
and metadata.st_mode & 0o022 == 0
and os.access(path, os.X_OK)
)
def _run_isolated_wheel_builder(command: tuple[str, ...]) -> int:
process = subprocess.Popen( # noqa: S603 - absolute trusted launcher only.
command,
cwd=Path("/"),
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
start_new_session=True,
)
try:
return process.wait(timeout=620)
except subprocess.TimeoutExpired as exc:
try:
os.killpg(process.pid, signal.SIGKILL)
except ProcessLookupError:
pass
process.wait()
raise ReleaseExecutionBlocked(
"Isolated wheel builder exceeded its bounded execution time."
) from exc
def _single_isolated_wheel(output: Path) -> Path:
entries: list[Path] = []
with os.scandir(output) as iterator:
for entry in iterator:
entries.append(Path(entry.path))
if len(entries) > 1:
break
if len(entries) != 1:
raise ReleaseExecutionBlocked(
"Isolated builder must return exactly one wheel and no extra output."
)
wheel = entries[0]
metadata = wheel.lstat()
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISREG(metadata.st_mode)
or wheel.suffix != ".whl"
or metadata.st_uid != os.geteuid()
or metadata.st_nlink != 1
or not 0 < metadata.st_size <= MAX_WHEEL_BYTES
):
raise ReleaseExecutionBlocked(
"Isolated builder returned an unsafe or oversized wheel."
)
return wheel
def _copy_isolated_wheel(source: Path, destination: Path) -> None:
"""Copy worker output through pinned descriptors into a fresh candidate file."""
source_flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
destination_flags = (
os.O_WRONLY
| os.O_CREAT
| os.O_EXCL
| getattr(os, "O_NOFOLLOW", 0)
)
source_descriptor = os.open(source, source_flags)
destination_descriptor = -1
try:
metadata = os.fstat(source_descriptor)
if (
not stat.S_ISREG(metadata.st_mode)
or metadata.st_uid != os.geteuid()
or metadata.st_nlink != 1
or not 0 < metadata.st_size <= MAX_WHEEL_BYTES
):
raise ReleaseExecutionBlocked(
"Isolated builder wheel changed before trusted staging."
)
destination_descriptor = os.open(
destination,
destination_flags,
0o600,
)
remaining = metadata.st_size
while remaining:
chunk = os.read(source_descriptor, min(1024 * 1024, remaining))
if not chunk:
raise ReleaseExecutionBlocked(
"Isolated builder wheel was truncated during trusted staging."
)
view = memoryview(chunk)
while view:
written = os.write(destination_descriptor, view)
if written <= 0:
raise ReleaseExecutionBlocked(
"Isolated builder wheel could not be staged safely."
)
view = view[written:]
remaining -= len(chunk)
if os.read(source_descriptor, 1):
raise ReleaseExecutionBlocked(
"Isolated builder wheel grew beyond its verified size."
)
os.fchmod(destination_descriptor, 0o600)
os.fsync(destination_descriptor)
except BaseException:
if destination_descriptor >= 0:
os.close(destination_descriptor)
destination_descriptor = -1
destination.unlink(missing_ok=True)
raise
finally:
os.close(source_descriptor)
if destination_descriptor >= 0:
os.close(destination_descriptor)
directory_descriptor = os.open(
destination.parent,
os.O_RDONLY | getattr(os, "O_DIRECTORY", 0),
)
try:
os.fsync(directory_descriptor)
finally:
os.close(directory_descriptor)
def _clone_frozen_sources(
*,
repo_versions: dict[str, str],
source_receipts: dict[str, dict[str, Any]],
workspace_root: Path,
destination: Path,
) -> dict[str, Path]:
specs = {item.name: item for item in load_repository_specs(include_website=False)}
result: dict[str, Path] = {}
for repo in sorted(repo_versions):
repository = specs.get(repo)
receipt = source_receipts.get(repo)
if repository is None or not isinstance(receipt, dict):
raise ReleaseExecutionBlocked(
f"Repository {repo} has no verified frozen source receipt."
)
# Recheck the private local receipt and registered origin immediately
# before fetching the immutable source tag.
verify_frozen_repository_tag_receipt(
receipt=receipt,
workspace_root=workspace_root,
)
result[repo] = _checkout_frozen_source(
repo=repo,
source_remote=repository.remote,
commit=str(receipt["head"]),
tag=str(receipt["target_tag"]),
tag_object=str(receipt["tag_object"]),
source_root=destination,
)
return result
def _clone_catalog_sources(
*,
source_versions: dict[str, str],
repo_versions: dict[str, str],
source_receipts: dict[str, dict[str, Any]],
workspace_root: Path,
destination: Path,
) -> dict[str, Path]:
"""Clone every authenticated catalog source, overriding selected exact receipts."""
specs = {item.name: item for item in load_repository_specs(include_website=False)}
result: dict[str, Path] = {}
for repo, version in sorted(source_versions.items()):
repository = specs.get(repo)
if repository is None:
raise ReleaseExecutionBlocked(
f"Authenticated catalog source {repo} is not registered."
)
receipt = source_receipts.get(repo) if repo in repo_versions else None
if repo in repo_versions:
if not isinstance(receipt, dict):
raise ReleaseExecutionBlocked(
f"Selected repository {repo} has no verified source receipt."
)
verify_frozen_repository_tag_receipt(
receipt=receipt,
workspace_root=workspace_root,
)
expected_tag = f"v{version.removeprefix('v')}"
if receipt.get("target_tag") != expected_tag:
raise ReleaseExecutionBlocked(
f"Selected repository {repo} receipt has the wrong release tag."
)
result[repo] = _checkout_frozen_source(
repo=repo,
source_remote=repository.remote,
commit=str(receipt["head"]),
tag=expected_tag,
tag_object=str(receipt["tag_object"]),
source_root=destination,
)
else:
result[repo] = _checkout_registered_source_tag(
repo=repo,
source_remote=repository.remote,
tag=f"v{version.removeprefix('v')}",
source_root=destination,
)
return result
def verify_frozen_repository_tag_receipt(
*, receipt: dict[str, Any] | None, workspace_root: Path
) -> dict[str, Any]:
"""Verify a stored local/remote annotated tag without trusting live HEAD."""
if not isinstance(receipt, dict) or receipt.get("kind") != "repository_state":
raise ReleaseExecutionBlocked(
"Catalog generation requires each source push's durable receipt."
)
repo = receipt.get("repo")
tag = receipt.get("target_tag")
tag_object = receipt.get("tag_object")
head = receipt.get("head")
if not all(isinstance(value, str) and value for value in (repo, tag, tag_object, head)):
raise ReleaseExecutionBlocked("Frozen source receipt is incomplete.")
specs = {item.name: item for item in load_repository_specs(include_website=False)}
repository = specs.get(repo)
if repository is None:
raise ReleaseExecutionBlocked("Frozen source repository is not registered.")
path = resolve_repo_path(repository, workspace_root)
_require_operator_checkout(path)
fetch_urls = _bounded_remote_urls(path, push=False)
push_urls = _bounded_remote_urls(path, push=True)
if fetch_urls != (repository.remote.strip(),) or push_urls != (
repository.remote.strip(),
):
raise ReleaseExecutionBlocked(
"Frozen source origin no longer matches its registered release remote."
)
remote_digest = hashlib.sha256(
json.dumps(
{"fetch": fetch_urls, "push": push_urls},
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
).encode("utf-8")
).hexdigest()
if receipt.get("remote") != DURABLE_REMOTE or receipt.get(
"remote_sha256"
) != remote_digest:
raise ReleaseExecutionBlocked("Frozen source remote binding changed.")
local_object = git_text(path, "rev-parse", "--verify", f"refs/tags/{tag}")
local_type = git_text(path, "cat-file", "-t", f"refs/tags/{tag}")
local_commit = ref_commit(path, f"refs/tags/{tag}")
remote = remote_tag_commit(path, remote=DURABLE_REMOTE, tag=tag)
if (
local_type != "tag"
or local_object != tag_object
or local_commit != head
or remote.error
or not remote.annotated
or remote.tag_object != tag_object
or remote.commit != head
):
raise ReleaseExecutionBlocked(
"Frozen source tag is not the same annotated object locally and on origin."
)
return dict(receipt)
def _checkout_frozen_source(
*,
repo: str,
source_remote: str | Path,
commit: str,
tag: str,
tag_object: str,
source_root: Path,
) -> Path:
destination = source_root / repo
clone = subprocess.run(
(
"/usr/bin/git",
"clone",
"--quiet",
"--no-checkout",
"--no-hardlinks",
str(source_remote),
str(destination),
),
cwd=source_root,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=120,
env=_git_release_environment(),
)
if clone.returncode != 0:
raise ReleaseExecutionBlocked(
f"Frozen source clone failed for {repo}."
)
cloned_tag_object = git_text(
destination, "rev-parse", "--verify", f"refs/tags/{tag}"
)
cloned_tag_commit = ref_commit(destination, f"refs/tags/{tag}")
cloned_tag_type = git_text(destination, "cat-file", "-t", f"refs/tags/{tag}")
if (
cloned_tag_type != "tag"
or cloned_tag_object != tag_object
or cloned_tag_commit != commit
):
raise ReleaseExecutionBlocked(
f"Registered origin returned a different release tag for {repo}."
)
checkout = subprocess.run(
("/usr/bin/git", "checkout", "--quiet", "--detach", commit),
cwd=destination,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=120,
env=_git_release_environment(),
)
if checkout.returncode != 0 or git_text(
destination, "rev-parse", "--verify", "HEAD"
) != commit:
raise ReleaseExecutionBlocked(
f"Frozen source checkout failed for {repo}: "
f"{_compact_output(checkout.stderr) or 'commit identity mismatch'}"
)
return destination
def _checkout_registered_source_tag(
*,
repo: str,
source_remote: str | Path,
tag: str,
source_root: Path,
) -> Path:
"""Clone one unchanged authenticated-base source at its annotated origin tag."""
destination = source_root / repo
clone = subprocess.run(
(
"/usr/bin/git",
"clone",
"--quiet",
"--no-checkout",
"--no-hardlinks",
str(source_remote),
str(destination),
),
cwd=source_root,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=120,
env=_git_release_environment(),
)
if clone.returncode != 0:
raise ReleaseExecutionBlocked(
f"Authenticated catalog source clone failed for {repo}."
)
tag_object = git_text(destination, "rev-parse", "--verify", f"refs/tags/{tag}")
tag_commit = ref_commit(destination, f"refs/tags/{tag}")
if (
git_text(destination, "cat-file", "-t", f"refs/tags/{tag}") != "tag"
or len(tag_object) not in {40, 64}
or not tag_commit
):
raise ReleaseExecutionBlocked(
f"Authenticated catalog source tag is not annotated for {repo}."
)
checkout = subprocess.run(
("/usr/bin/git", "checkout", "--quiet", "--detach", tag_commit),
cwd=destination,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=120,
env=_git_release_environment(),
)
if checkout.returncode != 0 or git_text(
destination, "rev-parse", "--verify", "HEAD"
) != tag_commit:
raise ReleaseExecutionBlocked(
f"Authenticated catalog source checkout failed for {repo}."
)
return destination
def _git_release_environment() -> dict[str, str]:
environment = sanitized_git_environment()
environment.setdefault(
"GIT_SSH_COMMAND",
"ssh -o BatchMode=yes -o ConnectTimeout=8",
)
return environment
def python_project_name(repository: Path) -> str | None:
pyproject = repository / "pyproject.toml"
try:
encoded = pyproject.read_bytes()
except FileNotFoundError:
return None
except OSError as exc:
raise ReleaseExecutionBlocked("Python project metadata cannot be read.") from exc
if len(encoded) > 1024 * 1024:
raise ReleaseExecutionBlocked("Python project metadata exceeds its size limit.")
try:
payload = tomllib.loads(encoded.decode("utf-8"))
except (UnicodeDecodeError, tomllib.TOMLDecodeError) as exc:
raise ReleaseExecutionBlocked("Python project metadata is malformed.") from exc
project = payload.get("project")
name = project.get("name") if isinstance(project, dict) else None
return name.strip() if isinstance(name, str) and name.strip() else None
def publish_received_candidate(
*,
candidate_path: Path,
candidate_receipt: dict[str, Any],
channel: str,
workspace_root: Path,
remote: str,
expected_website_receipt: dict[str, Any],
) -> tuple[dict[str, Any], dict[str, Any]]:
require_trusted_release_runtime(workspace_root=workspace_root)
website_binding = _validated_website_binding(expected_website_receipt)
if remote != DURABLE_REMOTE:
raise ReleaseExecutionBlocked("Catalog publication uses only origin.")
try:
result = publish_catalog_candidate(
candidate_dir=candidate_path,
workspace_root=workspace_root,
channel=channel,
apply=True,
commit=True,
tag=True,
push=True,
remote=remote,
source_remote=remote,
expected_website_head=str(website_binding["head"]),
expected_website_branch=str(website_binding["branch"]),
expected_remote_sha256=str(website_binding["remote_sha256"]),
)
except Exception as exc: # noqa: BLE001 - partial Git effects are ambiguous.
raise ReleaseExecutionAmbiguous(
f"Catalog publication raised {type(exc).__name__}; reconcile website and remote state."
) from exc
payload = to_jsonable(result)
status = payload.get("status") if isinstance(payload, dict) else None
if status == "published":
try:
receipt = reconciled_catalog_publication_receipt(
candidate_path=candidate_path,
candidate_receipt=candidate_receipt,
channel=channel,
workspace_root=workspace_root,
remote=remote,
expected_website_receipt=website_binding,
)
_require_publisher_result_matches_receipt(payload, receipt)
except (ReleaseExecutionBlocked, OSError, RuntimeError, ValueError) as exc:
raise ReleaseExecutionAmbiguous(
"Catalog publisher reported success, but its exact commit, derived "
"files, annotated tag, and origin state could not be proved."
) from exc
return payload, receipt
if status == "blocked":
raise ReleaseExecutionBlocked(_bounded_result_detail(payload, "catalog publication"))
raise ReleaseExecutionAmbiguous(_bounded_result_detail(payload, "catalog publication"))
def reconciled_catalog_publication_receipt(
*,
candidate_path: Path,
candidate_receipt: dict[str, Any],
channel: str,
workspace_root: Path,
remote: str,
expected_website_receipt: dict[str, Any],
) -> dict[str, Any]:
"""Independently prove an exact candidate publication after interruption."""
require_trusted_release_runtime(workspace_root=workspace_root)
if remote != DURABLE_REMOTE:
raise ReleaseExecutionBlocked("Catalog reconciliation uses only origin.")
website_binding = _validated_website_binding(expected_website_receipt)
candidate_id, expected_catalog_hash = _validated_catalog_candidate_receipt(
candidate_receipt
)
channel = validate_release_channel(channel)
catalog_payload = _read_generated_catalog(candidate_path, channel=channel)
catalog_hash = canonical_hash(catalog_payload)
if catalog_hash != expected_catalog_hash:
raise ReleaseExecutionBlocked(
"Catalog candidate changed after its durable generator receipt."
)
keyring_payload = _read_candidate_json(
candidate_path / "keyring.json",
label="candidate keyring",
)
keyring_hash = canonical_hash(keyring_payload)
release = catalog_payload.get("release")
if (
not isinstance(release, dict)
or release.get("keyring_sha256") != keyring_hash
):
raise ReleaseExecutionBlocked(
"Signed catalog does not pin the exact candidate keyring."
)
artifact_issues = selected_artifact_identity_issues(catalog_payload)
if artifact_issues:
raise ReleaseExecutionBlocked(
"Candidate release artifact identity is incomplete: "
+ "; ".join(artifact_issues)
)
web_root = website_root(workspace_root)
current = repository_state_receipt(
repo="addideas-govoplan-website",
target_tag="",
workspace_root=workspace_root,
include_website=True,
)
if (
current.get("branch") != website_binding["branch"]
or current.get("remote") != website_binding["remote"]
or current.get("remote_sha256") != website_binding["remote_sha256"]
or current.get("worktree_clean") is not True
):
raise ReleaseExecutionBlocked(
"Website checkout does not match the frozen publication boundary."
)
frozen_remote = bind_publication_remote(
web_root=web_root,
remote=remote,
registered_remote=registered_website_remote(),
)
if frozen_remote.sha256 != website_binding["remote_sha256"]:
raise ReleaseExecutionBlocked(
"Website origin changed after the publication plan was frozen."
)
tag_name = default_tag_name(catalog_payload, channel=channel)
if not tag_name:
raise ReleaseExecutionBlocked(
"Catalog publication tag cannot be reconstructed from the candidate."
)
identity = remote_publication_identity(
web_root=web_root,
remote_url=frozen_remote.url,
branch=str(website_binding["branch"]),
tag_name=tag_name,
)
commit = identity["publication_commit_sha"]
tag_object = identity["publication_tag_object_sha"]
if (
identity["publication_tag_commit_sha"] != commit
or current.get("head") != commit
):
raise ReleaseExecutionBlocked(
"Remote catalog branch and annotated tag do not identify one local commit."
)
if (
git_text(web_root, "cat-file", "-t", f"refs/tags/{tag_name}") != "tag"
or git_text(
web_root,
"rev-parse",
"--verify",
f"refs/tags/{tag_name}",
)
!= tag_object
or ref_commit(web_root, f"refs/tags/{tag_name}") != commit
):
raise ReleaseExecutionBlocked(
"Local catalog publication tag differs from the origin annotation."
)
parent_line = git_text(web_root, "rev-list", "--parents", "-n", "1", commit)
if parent_line.split() != [commit, str(website_binding["head"])]:
raise ReleaseExecutionBlocked(
"Catalog publication commit does not have the sole frozen parent."
)
previous_keyring = _read_git_json_blob(
web_root,
commit=str(website_binding["head"]),
repository_path="public/catalogs/v1/keyring.json",
label="frozen website keyring",
)
trusted_keys = trusted_keys_from_keyring(previous_keyring)
if not trusted_keys:
raise ReleaseExecutionBlocked(
"Frozen website commit has no catalog trust anchor."
)
candidate_keys = trusted_keys_from_keyring(keyring_payload)
for key_id in set(candidate_keys) & set(trusted_keys):
if candidate_keys[key_id] != trusted_keys[key_id]:
raise ReleaseExecutionBlocked(
"Candidate keyring replaces an existing trusted public key."
)
validation = validate_catalog_payload(
catalog_payload,
require_trusted=True,
approved_channels=(channel,),
trusted_keys=trusted_keys,
)
if validation.get("valid") is not True:
raise ReleaseExecutionBlocked(
"Candidate does not validate against the frozen website trust anchor."
)
expected_blobs, module_root = _expected_catalog_publication_blobs(
web_root=web_root,
catalog_payload=catalog_payload,
keyring_payload=keyring_payload,
channel=channel,
)
_verify_exact_publication_delta(
web_root=web_root,
frozen_parent=str(website_binding["head"]),
commit=commit,
expected_blobs=expected_blobs,
module_root=module_root,
)
verify_committed_publication(
web_root=web_root,
commit_sha=commit,
expected_blobs=expected_blobs,
module_root=module_root,
)
return {
"kind": "catalog_publication",
"candidate_id": candidate_id,
"catalog_sha256": catalog_hash,
"keyring_sha256": keyring_hash,
"publication_commit_sha": commit,
"publication_tag_object_sha": tag_object,
"publication_tag_commit_sha": commit,
"branch": str(website_binding["branch"]),
"tag_name": tag_name,
"remote": remote,
"remote_sha256": str(website_binding["remote_sha256"]),
}
def _validated_website_binding(value: object) -> dict[str, Any]:
if (
not isinstance(value, dict)
or value.get("kind") != "repository_state"
or value.get("repo") != "addideas-govoplan-website"
or value.get("remote") != DURABLE_REMOTE
or value.get("target_tag") != ""
or value.get("tag_object") is not None
or value.get("worktree_clean") is not True
or not isinstance(value.get("head"), str)
or len(value["head"]) not in {40, 64}
or not isinstance(value.get("branch"), str)
or not value["branch"]
or not isinstance(value.get("remote_sha256"), str)
or len(value["remote_sha256"]) != 64
):
raise ReleaseExecutionBlocked(
"Catalog publication has no valid frozen website receipt."
)
return dict(value)
def _validated_catalog_candidate_receipt(value: object) -> tuple[str, str]:
if (
not isinstance(value, dict)
or set(value) != {"kind", "candidate_id", "catalog_sha256"}
or value.get("kind") != "catalog_candidate"
or not isinstance(value.get("candidate_id"), str)
or not value["candidate_id"].startswith("candidate-")
or len(value["candidate_id"]) != 42
or not isinstance(value.get("catalog_sha256"), str)
or len(value["catalog_sha256"]) != 64
):
raise ReleaseExecutionBlocked(
"Catalog publication has no valid generator receipt."
)
return value["candidate_id"], value["catalog_sha256"]
def _expected_catalog_publication_blobs(
*,
web_root: Path,
catalog_payload: dict[str, Any],
keyring_payload: dict[str, Any],
channel: str,
) -> tuple[dict[str, bytes], Path]:
catalog_root = web_root / "public" / "catalogs" / "v1"
module_root = catalog_root / "modules"
payloads = module_directory_payloads(
catalog_payload=catalog_payload,
keyring_payload=keyring_payload,
channel=channel,
public_base_url=catalog_public_base_url(catalog_payload),
)
expected = {
f"public/catalogs/v1/channels/{channel}.json": _public_json_bytes(
catalog_payload
),
"public/catalogs/v1/keyring.json": _public_json_bytes(keyring_payload),
}
for relative_path, payload in payloads:
repository_path = (Path("public/catalogs/v1") / relative_path).as_posix()
if repository_path in expected:
raise ReleaseExecutionBlocked(
"Derived catalog publication paths are ambiguous."
)
expected[repository_path] = _public_json_bytes(payload)
return expected, module_root
def _verify_exact_publication_delta(
*,
web_root: Path,
frozen_parent: str,
commit: str,
expected_blobs: dict[str, bytes],
module_root: Path,
) -> None:
"""Require the publication commit to change only the derived catalog tree."""
try:
module_prefix = module_root.absolute().relative_to(web_root.absolute()).as_posix()
except ValueError as exc:
raise ReleaseExecutionBlocked(
"Catalog module root leaves the website checkout."
) from exc
tree = git(
web_root,
"ls-tree",
"-r",
"-z",
frozen_parent,
"--",
"public/catalogs/v1/channels",
"public/catalogs/v1/keyring.json",
module_prefix,
timeout=30,
)
tree_bytes = tree.stdout.encode("utf-8", errors="strict")
if tree.returncode != 0 or len(tree_bytes) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(
"Frozen catalog tree cannot be inspected within its bounds."
)
old_blobs: dict[str, str] = {}
for row in (item for item in tree.stdout.split("\0") if item):
metadata, separator, repository_path = row.partition("\t")
fields = metadata.split()
if (
separator != "\t"
or len(fields) != 3
or fields[0] != "100644"
or fields[1] != "blob"
or len(fields[2]) not in {40, 64}
or repository_path in old_blobs
):
raise ReleaseExecutionBlocked(
"Frozen catalog tree contains an ambiguous entry."
)
old_blobs[repository_path] = fields[2]
object_format = git_text(web_root, "rev-parse", "--show-object-format")
if object_format not in {"sha1", "sha256"}:
raise ReleaseExecutionBlocked(
"Website Git object format cannot be established."
)
expected_delta = {
path
for path, encoded in expected_blobs.items()
if old_blobs.get(path) != _git_blob_identity(encoded, object_format)
}
expected_delta.update(
path
for path in old_blobs
if path.startswith(f"{module_prefix}/") and path not in expected_blobs
)
observed = git(
web_root,
"diff-tree",
"--no-commit-id",
"--name-only",
"--no-renames",
"--no-ext-diff",
"-r",
"-z",
frozen_parent,
commit,
timeout=30,
)
observed_bytes = observed.stdout.encode("utf-8", errors="strict")
if observed.returncode != 0 or len(observed_bytes) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(
"Catalog publication delta cannot be inspected within its bounds."
)
observed_delta = {path for path in observed.stdout.split("\0") if path}
if observed_delta != expected_delta:
raise ReleaseExecutionBlocked(
"Catalog publication commit contains paths outside the exact derived delta."
)
def _git_blob_identity(encoded: bytes, object_format: str) -> str:
digest = hashlib.new(object_format)
digest.update(f"blob {len(encoded)}\0".encode("ascii"))
digest.update(encoded)
return digest.hexdigest()
def _public_json_bytes(payload: object) -> bytes:
encoded = (
json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=True) + "\n"
).encode("utf-8")
if len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(
"Derived catalog publication object exceeds its size limit."
)
return encoded
def _read_git_json_blob(
repository: Path,
*,
commit: str,
repository_path: str,
label: str,
) -> dict[str, Any]:
entry = git_text(
repository,
"ls-tree",
commit,
"--",
repository_path,
)
metadata, separator, observed_path = entry.partition("\t")
fields = metadata.split()
if (
separator != "\t"
or observed_path != repository_path
or len(fields) != 3
or fields[0] != "100644"
or fields[1] != "blob"
or len(fields[2]) not in {40, 64}
):
raise ReleaseExecutionBlocked(f"{label.capitalize()} is not one regular blob.")
result = git(repository, "cat-file", "blob", fields[2], timeout=30)
encoded = result.stdout.encode("utf-8", errors="strict")
if result.returncode != 0 or len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(f"{label.capitalize()} cannot be read safely.")
try:
payload = json.loads(encoded)
except json.JSONDecodeError as exc:
raise ReleaseExecutionBlocked(f"{label.capitalize()} is not valid JSON.") from exc
if not isinstance(payload, dict):
raise ReleaseExecutionBlocked(f"{label.capitalize()} is not a JSON object.")
return payload
def _read_candidate_json(path: Path, *, label: str) -> dict[str, Any]:
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
descriptor = -1
try:
descriptor = os.open(path, flags)
metadata = os.fstat(descriptor)
if (
not stat.S_ISREG(metadata.st_mode)
or metadata.st_uid != os.geteuid()
or metadata.st_mode & 0o077
):
raise ReleaseExecutionBlocked(
f"{label.capitalize()} is not a private regular file."
)
chunks: list[bytes] = []
remaining = 16 * 1024 * 1024 + 1
while remaining:
chunk = os.read(descriptor, min(1024 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
encoded = b"".join(chunks)
except OSError as exc:
raise ReleaseExecutionBlocked(
f"{label.capitalize()} cannot be read safely."
) from exc
finally:
if descriptor >= 0:
os.close(descriptor)
if len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked(f"{label.capitalize()} exceeds its size limit.")
try:
payload = json.loads(encoded.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ReleaseExecutionBlocked(f"{label.capitalize()} is not valid JSON.") from exc
if not isinstance(payload, dict):
raise ReleaseExecutionBlocked(f"{label.capitalize()} is not a JSON object.")
return payload
def _require_publisher_result_matches_receipt(
result: dict[str, Any], receipt: dict[str, Any]
) -> None:
fields = {
"candidate_catalog_hash": "catalog_sha256",
"candidate_keyring_hash": "keyring_sha256",
"publication_commit_sha": "publication_commit_sha",
"publication_tag_object_sha": "publication_tag_object_sha",
"publication_tag_commit_sha": "publication_tag_commit_sha",
"branch": "branch",
"tag_name": "tag_name",
"remote": "remote",
}
if any(result.get(source) != receipt[target] for source, target in fields.items()):
raise ReleaseExecutionBlocked(
"Catalog publisher result differs from independent publication proof."
)
def _known_or_ambiguous_result(
result: dict[str, object], *, success: set[str], operation: str
) -> dict[str, Any]:
payload = to_jsonable(result)
status = payload.get("status") if isinstance(payload, dict) else None
if status in success:
return payload
if status == "blocked":
raise ReleaseExecutionBlocked(_bounded_result_detail(payload, operation))
raise ReleaseExecutionAmbiguous(_bounded_result_detail(payload, operation))
def _bounded_result_detail(payload: dict[str, Any], operation: str) -> str:
repositories = payload.get("repositories")
details = [
str(item.get("detail"))
for item in repositories
if isinstance(item, dict) and item.get("detail")
] if isinstance(repositories, list) else []
detail = "; ".join(details) or str(payload.get("status") or "unknown result")
return _compact_output(f"{operation} did not establish success: {detail}")
def _read_generated_catalog(candidate: Path, *, channel: str) -> dict[str, Any]:
channel = validate_release_channel(channel)
path = candidate / "channels" / f"{channel}.json"
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = -1
try:
descriptor = os.open(path, flags)
metadata = os.fstat(descriptor)
if not stat.S_ISREG(metadata.st_mode):
raise ReleaseExecutionBlocked("Generated catalog is not a regular file.")
with os.fdopen(descriptor, "rb") as handle:
descriptor = -1
encoded = handle.read(16 * 1024 * 1024 + 1)
except OSError as exc:
raise ReleaseExecutionBlocked("Generated catalog cannot be read safely.") from exc
finally:
if descriptor >= 0:
os.close(descriptor)
if len(encoded) > 16 * 1024 * 1024:
raise ReleaseExecutionBlocked("Generated catalog exceeds its size limit.")
payload = json.loads(encoded.decode("utf-8"))
if not isinstance(payload, dict):
raise ReleaseExecutionBlocked("Generated catalog is not a JSON object.")
return payload
def _remove_partial_candidate(candidate: Path) -> None:
if not candidate.exists():
return
if candidate.is_symlink() or not candidate.is_dir():
raise ReleaseExecutionAmbiguous(
"Partial candidate path is unsafe and requires manual reconciliation."
)
shutil.rmtree(candidate)
def _compact_output(value: str) -> str:
text = value.strip()
return text if len(text) <= MAX_EXECUTOR_OUTPUT else text[:MAX_EXECUTOR_OUTPUT] + ""