feat(release): define commit-addressed bundle index
prd-number-check / require-numbered-prds (pull_request) Successful in 8s
lint / lint (push) Successful in 59s
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Waiting to run
test / coverage (pull_request) Blocked by required conditions
test / image-input-builds (pull_request) Has started running
test / unit (pull_request) Has started running

This commit is contained in:
2026-07-27 17:00:56 +00:00
parent 93190d5e0c
commit 46b7e06b37
3 changed files with 217 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
"""External, commit-addressed release bundle metadata."""
from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from typing import Any
from .release_manifest import ReleaseManifestError, parse_manifest
_COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
_SHA_RE = re.compile(r"^[0-9a-f]{64}$")
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
def build_bundle_index(
manifest_data: Any,
*,
wheel: Path,
wheel_url: str,
workflow_run: str,
published_at: str,
) -> dict[str, Any]:
"""Build and validate the immutable external index for one source commit."""
manifest = parse_manifest(manifest_data)
if not wheel.is_file() or wheel.suffix != ".whl":
raise ReleaseManifestError("release bundle wheel must be one .whl file")
if not wheel_url.startswith("https://") or not wheel_url.endswith(wheel.name):
raise ReleaseManifestError(
"release bundle wheel URL must be HTTPS and end with the wheel filename")
if not workflow_run.strip() or not published_at.strip():
raise ReleaseManifestError(
"release bundle provenance requires workflow_run and published_at")
return {
"schema": 1,
"source_commit": manifest.source_commit,
"wheel": {
"filename": wheel.name,
"url": wheel_url,
"sha256": sha256_file(wheel),
},
"oci": {
"orchestrator": manifest.orchestrator_image,
"gateway": manifest.gateway_image,
},
"firecracker": {
"orchestrator": {
"version": manifest.firecracker_orchestrator.version,
"sha256": manifest.firecracker_orchestrator.sha256,
},
"gateway": {
"version": manifest.firecracker_gateway.version,
"sha256": manifest.firecracker_gateway.sha256,
},
},
"provenance": {
"workflow_run": workflow_run.strip(),
"published_at": published_at.strip(),
},
"qualifications": [],
}
def parse_bundle_index(data: Any) -> dict[str, Any]:
"""Validate an index before publication or installer consumption."""
if not isinstance(data, dict) or data.get("schema") != 1:
raise ReleaseManifestError("release bundle index must use schema 1")
commit = data.get("source_commit")
if not isinstance(commit, str) or _COMMIT_RE.fullmatch(commit) is None:
raise ReleaseManifestError(
"release bundle source_commit must be 40 lowercase hex")
wheel = data.get("wheel")
if not isinstance(wheel, dict):
raise ReleaseManifestError("release bundle wheel must be an object")
filename, url, digest = (
wheel.get("filename"), wheel.get("url"), wheel.get("sha256"))
if not isinstance(filename, str) or not filename.endswith(".whl"):
raise ReleaseManifestError("release bundle wheel filename must end in .whl")
if not isinstance(url, str) or not url.startswith("https://") or not url.endswith(filename):
raise ReleaseManifestError(
"release bundle wheel URL must be HTTPS and match its filename")
if not isinstance(digest, str) or _SHA_RE.fullmatch(digest) is None:
raise ReleaseManifestError(
"release bundle wheel sha256 must be 64 lowercase hex")
manifest_data = {
"schema": 1,
"source_commit": commit,
"oci": data.get("oci"),
"firecracker": data.get("firecracker"),
}
parse_manifest(manifest_data)
provenance = data.get("provenance")
if not isinstance(provenance, dict) or not all(
isinstance(provenance.get(key), str) and provenance[key].strip()
for key in ("workflow_run", "published_at")
):
raise ReleaseManifestError("release bundle provenance is incomplete")
qualifications = data.get("qualifications")
if not isinstance(qualifications, list):
raise ReleaseManifestError("release bundle qualifications must be an array")
return data
def canonical_bytes(data: Any) -> bytes:
parse_bundle_index(data)
return (json.dumps(data, indent=2, sort_keys=True) + "\n").encode("utf-8")
__all__ = [
"build_bundle_index",
"canonical_bytes",
"parse_bundle_index",
"sha256_file",
]
+34
View File
@@ -0,0 +1,34 @@
"""Generate the external commit-addressed release bundle index."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from bot_bottle.release_bundle import build_bundle_index, canonical_bytes
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--manifest", required=True, type=Path)
parser.add_argument("--wheel", required=True, type=Path)
parser.add_argument("--wheel-url", required=True)
parser.add_argument("--workflow-run", required=True)
parser.add_argument("--published-at", required=True)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args(argv)
manifest = json.loads(args.manifest.read_text(encoding="utf-8"))
index = build_bundle_index(
manifest,
wheel=args.wheel,
wheel_url=args.wheel_url,
workflow_run=args.workflow_run,
published_at=args.published_at,
)
args.output.write_bytes(canonical_bytes(index))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+60
View File
@@ -0,0 +1,60 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from bot_bottle.release_bundle import (
build_bundle_index,
canonical_bytes,
parse_bundle_index,
)
from bot_bottle.release_manifest import ReleaseManifestError
from tests.unit.test_release_manifest import manifest
class TestReleaseBundle(unittest.TestCase):
def test_builds_commit_addressed_index(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
wheel = Path(tmp) / "bot_bottle-0.1.0-py3-none-any.whl"
wheel.write_bytes(b"wheel")
index = build_bundle_index(
manifest(),
wheel=wheel,
wheel_url=f"https://packages.example/{wheel.name}",
workflow_run="actions/123",
published_at="2026-07-27T00:00:00Z",
)
self.assertEqual("1" * 40, index["source_commit"])
self.assertEqual(64, len(index["wheel"]["sha256"]))
self.assertEqual([], index["qualifications"])
self.assertIs(index, parse_bundle_index(index))
self.assertTrue(canonical_bytes(index).endswith(b"\n"))
def test_rejects_mutable_or_mismatched_wheel_url(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
wheel = Path(tmp) / "release.whl"
wheel.write_bytes(b"wheel")
with self.assertRaisesRegex(ReleaseManifestError, "HTTPS"):
build_bundle_index(
manifest(),
wheel=wheel,
wheel_url="http://packages.example/other.whl",
workflow_run="actions/123",
published_at="now",
)
def test_rejects_index_with_changed_runtime_identity(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
wheel = Path(tmp) / "release.whl"
wheel.write_bytes(b"wheel")
index = build_bundle_index(
manifest(),
wheel=wheel,
wheel_url=f"https://packages.example/{wheel.name}",
workflow_run="actions/123",
published_at="now",
)
index["oci"]["gateway"] = "registry/gateway:latest"
with self.assertRaisesRegex(ReleaseManifestError, "digest-pinned"):
parse_bundle_index(index)