Files
ai-contract-platform/yuqei-infra/scripts/build-release-candidate-manifest.py
T

655 lines
28 KiB
Python

#!/usr/bin/env python3
"""Build a deterministic release manifest from one clean, explicit Git commit."""
from __future__ import annotations
import argparse
import ast
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
import tomllib
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from typing import Any, Iterable
SCHEMA_VERSION = "yuqei.release-source-manifest.v1"
FULL_COMMIT_RE = re.compile(r"(?:[0-9a-f]{40}|[0-9a-f]{64})")
BASELINE_PATH = "docs/qa/multiscene-closeout-20260910/delivery-baseline-v1.json"
PROMPT_DEFINITION_PATHS = (
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/drafting_prompt_modules.json",
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/review_scene_modules.json",
)
PYPROJECT_PATHS = (
"yuqei-contract-platform/services/contract-api/pyproject.toml",
"yuqei-ai-platform/services/ai-platform-api/pyproject.toml",
"yuqei-ai-platform/services/worker/pyproject.toml",
"yuqei-ai-sdk-python/pyproject.toml",
)
OPENAPI_PATHS = (
"yuqei-contract-platform/packages/contract-openapi/openapi.yaml",
"yuqei-ai-platform/packages/ai-openapi/openapi.yaml",
)
REQUIREMENT_LOCK_PATHS = (
"yuqei-infra/requirements/contract-api-linux-x86_64-py312.txt",
"yuqei-infra/requirements/ai-runtime-linux-x86_64-py312.txt",
)
_WINDOWS_GIT_EXECUTABLE = (
None
if sys.platform.startswith("linux")
else (
str(Path(discovered).resolve())
if (discovered := shutil.which("git")) is not None
else None
)
)
RELEASE_CONTROL_PATHS = (
".github/workflows/build-test-quality-gate.yml",
".github/workflows/security-quality-gate.yml",
"yuqei-infra/scripts/build-release-candidate-manifest.py",
"yuqei-infra/scripts/build-release-worktree-ownership.py",
"yuqei-infra/compose/docker-compose.production.offline.yml",
"yuqei-infra/deploy/preflight-production.py",
"yuqei-infra/deploy/verify-production-compose.py",
"yuqei-infra/deploy/release-package.py",
"yuqei-infra/deploy/plan-first-release-snapshot-restore.py",
"yuqei-infra/deploy/plan-rollback-rehearsal.py",
"yuqei-infra/release-trust/third-party-materials-baseline.json",
"yuqei-infra/release/linux-release-security-policy.v1.json",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
"yuqei-infra/scripts/build-infrastructure-image-bundle.py",
"yuqei-infra/scripts/build-third-party-materials.py",
"yuqei-infra/tests/test_production_compose_preflight.py",
"yuqei-infra/tests/test_production_compose_merge_gate.py",
"yuqei-infra/tests/test_release_package_executor.py",
"yuqei-infra/tests/test_linux_release_build_tools.py",
"yuqei-infra/tests/test_installable_release_assembler_hardening.py",
"yuqei-infra/tests/test_infrastructure_image_bundle.py",
"yuqei-infra/tests/test_first_release_snapshot_restore_planner.py",
"yuqei-infra/tests/test_rollback_rehearsal_planner.py",
"yuqei-infra/tests/test_third_party_materials.py",
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
)
CONTRACT_LOCK_PATH = REQUIREMENT_LOCK_PATHS[0]
CONTRACT_DOCKERFILE_PATH = "yuqei-infra/docker/contract-api.Dockerfile"
REQUIRED_PATHS = (
"package.json",
"package-lock.json",
"tsconfig.base.json",
*PYPROJECT_PATHS,
*OPENAPI_PATHS,
*PROMPT_DEFINITION_PATHS,
*REQUIREMENT_LOCK_PATHS,
*RELEASE_CONTROL_PATHS,
BASELINE_PATH,
"yuqei-ai-sdk-python/src/yuqei_sdk/__init__.py",
"yuqei-ai-sdk-python/src/yuqei_sdk/ai_platform.py",
"yuqei-ai-sdk-python/src/yuqei_sdk/contract_drafting.py",
"yuqei-ai-sdk-python/src/yuqei_sdk/draft_semantic_review.py",
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/main.py",
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/config.py",
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/drafting_prompt_modules.py",
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/review_scene_prompts.py",
"yuqei-ai-platform/services/worker/src/yuqei_worker/main.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/main.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/config.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_templates.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_scene_assets.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/api/drafting.py",
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/api/review.py",
"yuqei-contract-platform/services/contract-api/migrations/versions/20260909_0079_add_party_profiles.py",
"yuqei-ai-platform/services/ai-platform-api/migrations/versions/20260826_0028_add_stratified_quality_reviews.py",
"yuqei-contract-platform/apps/contract-web/package.json",
"yuqei-contract-platform/apps/contract-web/src/app/page.tsx",
"yuqei-contract-platform/apps/contract-web/src/app/contracts/[id]/page.tsx",
"yuqei-contract-platform/apps/contract-web/src/app/api/contract-platform/drafting-workspaces/[[...segments]]/route.ts",
"yuqei-contract-platform/apps/contract-web/src/app/api/ai-platform/prompt-templates/route.ts",
"yuqei-contract-platform/packages/design-system/package.json",
"yuqei-contract-platform/packages/design-system/src/index.ts",
"yuqei-infra/docker/contract-api.Dockerfile",
"yuqei-infra/docker/ai-platform-api.Dockerfile",
"yuqei-infra/docker/worker.Dockerfile",
"yuqei-infra/docker/contract-web.Dockerfile",
"yuqei-infra/compose/docker-compose.yml",
"yuqei-infra/compose/docker-compose.production.yml",
"yuqei-infra/nginx/templates/default.conf.template",
)
REQUIRED_PREFIXES = (
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/template_assets/v1_contract_templates/",
"yuqei-contract-platform/services/contract-api/migrations/versions/",
"yuqei-ai-platform/services/ai-platform-api/migrations/versions/",
"yuqei-contract-platform/apps/contract-web/src/app/api/",
)
SECRET_NAMED_SOURCE_SUFFIXES = {
".cjs",
".go",
".java",
".js",
".mjs",
".py",
".pyi",
".rs",
".ts",
".tsx",
}
SOURCE_TREE_COMPONENTS = {"src", "tests", "migrations"}
class ManifestError(RuntimeError):
"""Raised when a candidate cannot truthfully receive a source manifest."""
@dataclass(frozen=True)
class TreeEntry:
mode: str
object_id: str
size: int
path: str
def _controlled_git_environment() -> dict[str, str]:
if sys.platform.startswith("linux"):
environment = {
"HOME": "/var/empty",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"PATH": "/usr/bin:/bin",
}
else:
environment = {
key: os.environ[key]
for key in ("SYSTEMROOT", "WINDIR", "TEMP", "TMP")
if key in os.environ
}
if not _WINDOWS_GIT_EXECUTABLE:
raise ManifestError("Git executable is unavailable")
environment["PATH"] = str(Path(_WINDOWS_GIT_EXECUTABLE).parent)
environment.update({"LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"})
environment.update(
{
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_NO_REPLACE_OBJECTS": "1",
"GIT_OPTIONAL_LOCKS": "0",
"GIT_TERMINAL_PROMPT": "0",
}
)
return environment
def _git_command(repo: Path, *args: str) -> list[str]:
git_path = "/usr/bin/git" if sys.platform.startswith("linux") else _WINDOWS_GIT_EXECUTABLE
if not git_path:
raise ManifestError("Git executable is unavailable")
return [str(git_path), "--no-replace-objects", "-C", str(repo), *args]
def _git(repo: Path, *args: str, binary: bool = False) -> bytes | str:
completed = subprocess.run(
_git_command(repo, *args),
check=False,
capture_output=True,
env=_controlled_git_environment(),
timeout=30,
)
if completed.returncode:
message = completed.stderr.decode("utf-8", "replace").strip()
raise ManifestError(f"git {' '.join(args)} failed: {message}")
return completed.stdout if binary else completed.stdout.decode("utf-8", "strict").strip()
def _is_within(path: Path, parent: Path) -> bool:
try:
path.relative_to(parent)
except ValueError:
return False
return True
def validate_candidate_identity(repo: Path, commit: str, output: Path) -> str:
if not FULL_COMMIT_RE.fullmatch(commit):
raise ManifestError("--commit must be a full hexadecimal commit object id; names such as HEAD are refused")
replacement_refs = str(
_git(repo, "for-each-ref", "--format=%(refname)", "refs/replace/")
)
if replacement_refs:
raise ManifestError("repository contains forbidden refs/replace entries")
resolved = str(_git(repo, "rev-parse", "--verify", f"{commit}^{{commit}}"))
if resolved != commit:
raise ManifestError(f"commit object id did not resolve exactly: {commit} -> {resolved}")
head = str(_git(repo, "rev-parse", "HEAD"))
if head != commit:
raise ManifestError(f"HEAD {head} does not equal explicit candidate commit {commit}")
dirty = bytes(_git(repo, "status", "--porcelain=v1", "-z", "-uall", binary=True))
if dirty:
raise ManifestError("working tree is dirty; a working-tree snapshot must not be presented as a commit manifest")
if _is_within(output.resolve(), repo.resolve()):
raise ManifestError("manifest output must be outside the repository so generation does not dirty the candidate")
return resolved
def list_commit_tree(repo: Path, commit: str) -> list[TreeEntry]:
raw = bytes(_git(repo, "ls-tree", "-r", "-z", "--long", commit, binary=True))
entries: list[TreeEntry] = []
for record in raw.split(b"\0"):
if not record:
continue
metadata, raw_path = record.split(b"\t", 1)
mode, object_type, object_id, raw_size = metadata.decode("ascii").split()
if object_type != "blob" or raw_size == "-":
raise ManifestError(f"unsupported tree entry for {raw_path!r}: {object_type} {raw_size}")
entries.append(
TreeEntry(
mode=mode,
object_id=object_id,
size=int(raw_size),
path=raw_path.decode("utf-8", "strict"),
)
)
entries.sort(key=lambda item: item.path.encode("utf-8"))
return entries
def read_commit_blobs(repo: Path, entries: Iterable[TreeEntry]) -> dict[str, bytes]:
process = subprocess.Popen(
_git_command(repo, "cat-file", "--batch"),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=_controlled_git_environment(),
)
assert process.stdin is not None and process.stdout is not None and process.stderr is not None
blobs: dict[str, bytes] = {}
try:
for entry in entries:
process.stdin.write(entry.object_id.encode("ascii") + b"\n")
process.stdin.flush()
header = process.stdout.readline().rstrip(b"\n").decode("ascii")
parts = header.split()
if len(parts) != 3 or parts[0] != entry.object_id or parts[1] != "blob":
raise ManifestError(f"unexpected cat-file header for {entry.path}: {header}")
blob_size = int(parts[2])
content = process.stdout.read(blob_size)
terminator = process.stdout.read(1)
if terminator != b"\n" or len(content) != blob_size or blob_size != entry.size:
raise ManifestError(f"blob size mismatch for {entry.path}")
blobs[entry.path] = content
finally:
if process.stdin:
process.stdin.close()
process.wait(timeout=30)
if process.returncode:
raise ManifestError(process.stderr.read().decode("utf-8", "replace"))
return blobs
def forbidden_candidate_path(path: str) -> str | None:
pure = PurePosixPath(path)
lowered = path.casefold()
parts = [part.casefold() for part in pure.parts]
name = pure.name.casefold()
safe_template = any(token in name for token in (".example", ".sample", ".template", ".dist"))
if any(part in {"node_modules", "__pycache__", ".pytest_cache", ".mypy_cache", ".next", ".runtime"} for part in parts):
return "cache_or_runtime_directory"
if "dist-test" in parts or pure.suffix.casefold() in {".whl", ".sqlite", ".sqlite3", ".db", ".log", ".pid"}:
return "local_binary_or_runtime_file"
if pure.suffix.casefold() in {".pem", ".p12", ".pfx", ".jks", ".kdbx"}:
return "private_key_or_keystore_path"
if name in {"id_rsa", "id_ed25519", ".npmrc", ".pypirc", "credentials.json", "service-account.json"}:
return "credential_path"
if (name == ".env" or name.startswith(".env.")) and not safe_template:
return "non_template_environment_file"
if re.search(r"(?:^|/)(?:secrets?|credentials?)(?:[._-]|/)", lowered) and not safe_template:
source_named_for_secret_domain = (
pure.suffix.casefold() in SECRET_NAMED_SOURCE_SUFFIXES
and bool(SOURCE_TREE_COMPONENTS.intersection(parts))
)
if not source_named_for_secret_domain:
return "secret_like_path"
return None
def validate_tree_paths(entries: list[TreeEntry]) -> None:
paths = {entry.path for entry in entries}
missing = sorted(set(REQUIRED_PATHS) - paths)
missing_prefixes = sorted(prefix for prefix in REQUIRED_PREFIXES if not any(path.startswith(prefix) for path in paths))
forbidden = sorted((entry.path, reason) for entry in entries if (reason := forbidden_candidate_path(entry.path)))
if missing or missing_prefixes or forbidden:
details = {"missing_paths": missing, "missing_prefixes": missing_prefixes, "forbidden_paths": forbidden}
raise ManifestError("candidate tree validation failed: " + json.dumps(details, ensure_ascii=False))
def _sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _parse_lock(content: bytes) -> dict[str, Any]:
text = content.decode("utf-8")
requirements = re.findall(r"^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?==[^ ]+) \\$", text, re.MULTILINE)
hashes = re.findall(r"^\s+--hash=sha256:([0-9a-f]{64})$", text, re.MULTILINE)
if not requirements or len(requirements) != len(hashes):
raise ManifestError("Python requirement lock is not fully exact/hash pinned")
return {"package_count": len(requirements), "requirements": requirements, "hash_count": len(hashes)}
def dependency_summary(blobs: dict[str, bytes]) -> dict[str, Any]:
python_projects = []
for path in PYPROJECT_PATHS:
data = tomllib.loads(blobs[path].decode("utf-8"))
project = data["project"]
python_projects.append(
{
"path": path,
"name": project["name"],
"requires_python": project.get("requires-python"),
"dependencies": project.get("dependencies", []),
"sha256": _sha256(blobs[path]),
}
)
root_package = json.loads(blobs["package.json"])
package_lock = json.loads(blobs["package-lock.json"])
locks = []
for path in REQUIREMENT_LOCK_PATHS:
locks.append({"path": path, "sha256": _sha256(blobs[path]), **_parse_lock(blobs[path])})
return {
"python_projects": python_projects,
"python_locks": locks,
"node": {
"package_json_sha256": _sha256(blobs["package.json"]),
"package_lock_sha256": _sha256(blobs["package-lock.json"]),
"package_manager": root_package.get("packageManager"),
"workspaces": root_package.get("workspaces", []),
"lockfile_version": package_lock.get("lockfileVersion"),
"locked_package_entries": len(package_lock.get("packages", {})),
},
}
def runtime_compatibility_summary(blobs: dict[str, bytes]) -> dict[str, Any]:
contract_lock = blobs[CONTRACT_LOCK_PATH].decode("utf-8")
contract_dockerfile = blobs[CONTRACT_DOCKERFILE_PATH].decode("utf-8")
required_lock_markers = (
"glibc >= 2.28",
"manylinux_2_28_x86_64",
"manylinux2014_x86_64 fallback",
"pillow==12.3.0",
)
required_docker_markers = (
"YUQEI_MIN_GLIBC=2.28",
"platform.libc_ver()",
"actual >= minimum",
)
missing_lock_markers = [marker for marker in required_lock_markers if marker not in contract_lock]
missing_docker_markers = [marker for marker in required_docker_markers if marker not in contract_dockerfile]
if missing_lock_markers or missing_docker_markers:
raise ManifestError(
"Contract runtime compatibility policy is incomplete: "
+ json.dumps(
{
"missing_lock_markers": missing_lock_markers,
"missing_docker_markers": missing_docker_markers,
},
ensure_ascii=False,
)
)
return {
"python": "CPython 3.12",
"operating_system": "Linux",
"architecture": "x86_64",
"minimum_glibc": "2.28",
"wheel_platform_preference": ["manylinux_2_28_x86_64", "manylinux2014_x86_64"],
"build_guard": {
"path": CONTRACT_DOCKERFILE_PATH,
"policy": "platform.libc_ver() must report glibc >= 2.28 before dependency installation",
"sha256": _sha256(blobs[CONTRACT_DOCKERFILE_PATH]),
},
"contract_lock": {
"path": CONTRACT_LOCK_PATH,
"pillow_security_floor": "12.3.0",
"sha256": _sha256(blobs[CONTRACT_LOCK_PATH]),
},
}
def _openapi_counts(content: bytes) -> tuple[int, int]:
text = content.decode("utf-8")
path_count = len(re.findall(r"^ /[^\n:]+(?:\{[^\n]+\})?:\s*$", text, re.MULTILINE))
schema_block = text.split("\n schemas:\n", 1)
schema_count = 0
if len(schema_block) == 2:
for line in schema_block[1].splitlines():
if line and not line.startswith(" "):
break
if re.match(r"^ [A-Za-z0-9_.-]+:\s*$", line):
schema_count += 1
return path_count, schema_count
def openapi_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
result = []
for path in OPENAPI_PATHS:
paths, schemas = _openapi_counts(blobs[path])
result.append(
{"path": path, "sha256": _sha256(blobs[path]), "size": len(blobs[path]), "paths": paths, "schemas": schemas}
)
return result
def _literal_assignment(tree: ast.AST, name: str) -> Any:
for node in ast.walk(tree):
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == name for target in node.targets):
return ast.literal_eval(node.value)
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id == name:
return ast.literal_eval(node.value)
return None
def _prompt_module_records(payload: Any, path: str) -> list[dict[str, Any]]:
records = payload.get("modules") if isinstance(payload, dict) else payload
if not isinstance(records, list) or not all(isinstance(item, dict) for item in records):
raise ManifestError(f"Prompt definition must be a list or an object with a modules list: {path}")
return records
def migration_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
roots = (
"yuqei-contract-platform/services/contract-api/migrations/versions/",
"yuqei-ai-platform/services/ai-platform-api/migrations/versions/",
)
groups = []
for root in roots:
migrations = []
for path in sorted(path for path in blobs if path.startswith(root) and path.endswith(".py")):
tree = ast.parse(blobs[path].decode("utf-8"), filename=path)
revision = _literal_assignment(tree, "revision")
down_revision = _literal_assignment(tree, "down_revision")
if not isinstance(revision, str):
raise ManifestError(f"migration has no literal revision: {path}")
parents = [] if down_revision is None else [down_revision] if isinstance(down_revision, str) else list(down_revision)
migrations.append({"path": path, "revision": revision, "down_revisions": parents, "sha256": _sha256(blobs[path])})
revisions = {item["revision"] for item in migrations}
referenced = {parent for item in migrations for parent in item["down_revisions"] if parent in revisions}
groups.append({"root": root, "count": len(migrations), "heads": sorted(revisions - referenced), "migrations": migrations})
return groups
def asset_and_prompt_summary(blobs: dict[str, bytes]) -> dict[str, Any]:
baseline = json.loads(blobs[BASELINE_PATH])
asset_ids = [item["id"] for item in baseline.get("assets", [])]
baseline_prompt_keys = [
key
for section in baseline.get("prompt_modules", {}).values()
for key in section.get("keys", [])
]
prompt_files = []
actual_prompt_keys: list[str] = []
for path in PROMPT_DEFINITION_PATHS:
modules = _prompt_module_records(json.loads(blobs[path]), path)
keys = [item["key"] for item in modules]
prompt_files.append({"path": path, "sha256": _sha256(blobs[path]), "keys": keys})
actual_prompt_keys.extend(keys)
if len(asset_ids) != 60 or len(set(asset_ids)) != 60:
raise ManifestError(f"delivery baseline must contain 60 unique assets, found {len(asset_ids)}/{len(set(asset_ids))}")
if len(actual_prompt_keys) != 14 or len(set(actual_prompt_keys)) != 14:
raise ManifestError("Prompt definition files must contain 14 unique modules")
if set(baseline_prompt_keys) != set(actual_prompt_keys):
raise ManifestError("delivery baseline Prompt keys differ from source definition files")
asset_prefix = "yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/template_assets/v1_contract_templates/"
asset_files = sorted(path for path in blobs if path.startswith(asset_prefix))
return {
"baseline_path": BASELINE_PATH,
"baseline_sha256": _sha256(blobs[BASELINE_PATH]),
"asset_count": len(asset_ids),
"asset_ids": asset_ids,
"asset_source_file_count": len(asset_files),
"asset_source_tree_sha256": _sha256(
"\n".join(f"{path}\0{_sha256(blobs[path])}" for path in asset_files).encode("utf-8")
),
"prompt_module_count": len(actual_prompt_keys),
"prompt_keys": actual_prompt_keys,
"prompt_definition_files": prompt_files,
}
def configuration_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
selected = []
for path in sorted(blobs):
name = PurePosixPath(path).name
if (
name.endswith("Dockerfile")
or path.startswith("yuqei-infra/compose/")
or path.startswith("yuqei-infra/nginx/")
or path.startswith("yuqei-infra/deploy/")
or path.startswith("yuqei-infra/release/")
or path in RELEASE_CONTROL_PATHS
or name in {"pyproject.toml", "package.json", "package-lock.json", "alembic.ini", "tsconfig.base.json"}
or path in REQUIREMENT_LOCK_PATHS
):
selected.append({"path": path, "sha256": _sha256(blobs[path]), "size": len(blobs[path])})
return selected
def runtime_graph_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
components = (
("python_sdk", ("yuqei-ai-sdk-python/src/yuqei_sdk/",)),
("ai_api_worker", ("yuqei-ai-platform/services/ai-platform-api/src/", "yuqei-ai-platform/services/worker/src/")),
("contract_api", ("yuqei-contract-platform/services/contract-api/src/",)),
("web_bff_design_system", ("yuqei-contract-platform/apps/contract-web/src/", "yuqei-contract-platform/packages/design-system/src/")),
("openapi", OPENAPI_PATHS),
("migrations", ("yuqei-contract-platform/services/contract-api/migrations/", "yuqei-ai-platform/services/ai-platform-api/migrations/")),
(
"deployment_and_release_controls",
(
"yuqei-infra/docker/",
"yuqei-infra/compose/",
"yuqei-infra/nginx/",
"yuqei-infra/deploy/",
"yuqei-infra/release/",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
*RELEASE_CONTROL_PATHS,
),
),
)
result = []
for name, prefixes in components:
paths = sorted(path for path in blobs if any(path == prefix or path.startswith(prefix) for prefix in prefixes))
result.append(
{
"component": name,
"file_count": len(paths),
"total_size": sum(len(blobs[path]) for path in paths),
"content_set_sha256": _sha256(
"\n".join(f"{path}\0{_sha256(blobs[path])}" for path in paths).encode("utf-8")
),
"prefixes": list(prefixes),
}
)
return result
def build_manifest(repo: Path, commit: str, output: Path) -> dict[str, Any]:
validate_candidate_identity(repo, commit, output)
entries = list_commit_tree(repo, commit)
validate_tree_paths(entries)
blobs = read_commit_blobs(repo, entries)
file_records = [
{
"path": entry.path,
"mode": entry.mode,
"git_blob_oid": entry.object_id,
"size": entry.size,
"sha256": _sha256(blobs[entry.path]),
}
for entry in entries
]
content_index = "\n".join(
f"{item['path']}\0{item['mode']}\0{item['size']}\0{item['sha256']}" for item in file_records
).encode("utf-8")
commit_timestamp = str(_git(repo, "show", "-s", "--format=%cI", commit))
manifest = {
"schema_version": SCHEMA_VERSION,
"candidate": {
"commit": commit,
"commit_timestamp": commit_timestamp,
"tree": str(_git(repo, "rev-parse", f"{commit}^{{tree}}")),
"worktree_was_clean": True,
"identity_policy": "full_commit_oid_equals_HEAD",
},
"files": {
"count": len(file_records),
"total_size": sum(item["size"] for item in file_records),
"content_index_sha256": _sha256(content_index),
"items": file_records,
},
"summaries": {
"runtime_build_graph": runtime_graph_summary(blobs),
"runtime_compatibility": runtime_compatibility_summary(blobs),
"dependencies": dependency_summary(blobs),
"openapi": openapi_summary(blobs),
"migrations": migration_summary(blobs),
"configuration": configuration_summary(blobs),
"assets_and_prompts": asset_and_prompt_summary(blobs),
},
"claims_not_made": [
"No image was built or assigned a digest by this source-only tool.",
"No dependency vulnerability, deployment, database migration, publication, or legal approval is inferred.",
],
}
manifest["manifest_payload_sha256"] = _sha256(
json.dumps(manifest, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
)
return manifest
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo", type=Path, default=Path.cwd())
parser.add_argument("--commit", required=True, help="full Git commit object id; symbolic names are refused")
parser.add_argument("--output", type=Path, required=True, help="must be outside the repository")
args = parser.parse_args(argv)
try:
manifest = build_manifest(args.repo.resolve(), args.commit.casefold(), args.output.resolve())
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(
json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
newline="\n",
)
except ManifestError as error:
print(f"manifest_error: {error}", file=sys.stderr)
return 2
print(json.dumps({"output": str(args.output), "commit": args.commit, "files": manifest["files"]["count"]}))
return 0
if __name__ == "__main__":
raise SystemExit(main())