Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/release-blessing-surfaces.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"version": 1,
"surfaces": {
"python-backend": {
"display_name": "Python backend",
"blessing_tag_prefix": "python-backend-bless-",
"depends_on": [],
"promotion_workflows": [".github/workflows/gcp_backend.yml"]
},
"desktop-rust-backend": {
"display_name": "Desktop Rust backend",
"depends_on": ["python-backend"],
"promotion_workflows": [".github/workflows/desktop_promote_prod.yml"]
},
"desktop-macos": {
"display_name": "macOS Desktop app",
"depends_on": ["desktop-rust-backend", "python-backend"],
"promotion_workflows": [".github/workflows/desktop_promote_prod.yml"]
},
"flutter-ios": {
"display_name": "Flutter iOS app",
"depends_on": ["python-backend"],
"promotion_workflows": []
},
"flutter-android": {
"display_name": "Flutter Android app",
"depends_on": ["python-backend"],
"promotion_workflows": []
}
}
}
147 changes: 147 additions & 0 deletions .github/scripts/release_blessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""Shared release blessing metadata and dependency helpers."""

from __future__ import annotations

from dataclasses import dataclass
import json
from pathlib import Path

from desktop_release_metadata import fail


ROOT = Path(__file__).resolve().parents[2]
DEFAULT_REGISTRY_PATH = ROOT / ".github/release-blessing-surfaces.json"
TRUE_VALUES = {"true", "1", "yes"}


@dataclass(frozen=True)
class BlessingSurface:
id: str
display_name: str
depends_on: tuple[str, ...]
promotion_workflows: tuple[str, ...]


@dataclass(frozen=True)
class SurfaceBlessing:
surface_id: str
blessed: bool
sha: str
blessed_at: str
tier: str
evidence: str


def load_surface_registry(path: Path = DEFAULT_REGISTRY_PATH) -> dict[str, BlessingSurface]:
payload = json.loads(path.read_text(encoding="utf-8"))
raw_surfaces = payload.get("surfaces")
if not isinstance(raw_surfaces, dict) or not raw_surfaces:
fail(f"{path} must contain a non-empty surfaces object")

surfaces: dict[str, BlessingSurface] = {}
for surface_id, config in raw_surfaces.items():
if not isinstance(config, dict):
fail(f"surface {surface_id!r} must be an object")
depends_on = tuple(config.get("depends_on") or ())
promotion_workflows = tuple(config.get("promotion_workflows") or ())
surfaces[surface_id] = BlessingSurface(
id=surface_id,
display_name=str(config.get("display_name") or surface_id),
depends_on=depends_on,
promotion_workflows=promotion_workflows,
)

for surface in surfaces.values():
for dependency in surface.depends_on:
if dependency not in surfaces:
fail(f"surface {surface.id!r} depends on unknown surface {dependency!r}")
if dependency == surface.id:
fail(f"surface {surface.id!r} cannot depend on itself")

return surfaces


def namespaced_key(surface_id: str, field: str | None = None) -> str:
if field is None or field == "blessed":
return f"blessed.{surface_id}"
return f"blessed.{surface_id}.{field}"


def parse_bool(value: str) -> bool:
return value.strip().lower() in TRUE_VALUES


def surface_blessing_from_metadata(
metadata: dict[str, str],
surface_id: str,
*,
allow_legacy_desktop: bool = False,
) -> SurfaceBlessing:
if (
allow_legacy_desktop
and surface_id == "desktop-macos"
and namespaced_key(surface_id) not in metadata
and "blessed" in metadata
):
return SurfaceBlessing(
surface_id=surface_id,
blessed=parse_bool(metadata.get("blessed", "")),
sha=metadata.get("blessedSha", "").strip(),
blessed_at=metadata.get("blessedAt", "").strip(),
tier=metadata.get("blessedTier", "").strip(),
evidence=metadata.get("blessedEvidence", "").strip(),
)

return SurfaceBlessing(
surface_id=surface_id,
blessed=parse_bool(metadata.get(namespaced_key(surface_id), "")),
sha=metadata.get(namespaced_key(surface_id, "sha"), "").strip(),
blessed_at=metadata.get(namespaced_key(surface_id, "at"), "").strip(),
tier=metadata.get(namespaced_key(surface_id, "tier"), "").strip(),
evidence=metadata.get(namespaced_key(surface_id, "evidence"), "").strip(),
)


def require_surface_blessed(blessing: SurfaceBlessing) -> None:
if not blessing.blessed:
fail(f"{blessing.surface_id} must be blessed before prod promotion")
if not blessing.sha:
fail(f"{blessing.surface_id} blessing is missing sha metadata")
if not blessing.blessed_at:
fail(f"{blessing.surface_id} blessing is missing at metadata")


def dependency_closure(surface_id: str, surfaces: dict[str, BlessingSurface]) -> list[str]:
if surface_id not in surfaces:
fail(f"unknown blessing surface: {surface_id}")

ordered: list[str] = []
visiting: set[str] = set()
visited: set[str] = set()

def visit(current: str) -> None:
if current in visited:
return
if current in visiting:
fail(f"cycle detected in blessing surface dependencies at {current}")
visiting.add(current)
for dependency in surfaces[current].depends_on:
visit(dependency)
visiting.remove(current)
visited.add(current)
ordered.append(current)

visit(surface_id)
return ordered


def require_blessed_closure(surface_id: str, blessings: dict[str, SurfaceBlessing]) -> None:
surfaces = load_surface_registry()
for required_surface in dependency_closure(surface_id, surfaces):
blessing = blessings.get(required_surface)
if blessing is None:
fail(f"{surface_id} promotion is missing blessing data for {required_surface}")
if blessing.surface_id != required_surface:
fail(f"{surface_id} promotion has {blessing.surface_id} blessing data for {required_surface}")
require_surface_blessed(blessing)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
133 changes: 133 additions & 0 deletions .github/scripts/test_release_blessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Tests for shared release blessing helpers."""

from __future__ import annotations

from pathlib import Path
import sys


SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))

from release_blessing import ( # noqa: E402
SurfaceBlessing,
dependency_closure,
load_surface_registry,
require_blessed_closure,
surface_blessing_from_metadata,
)


def test_registry_loads_and_orders_dependencies() -> None:
surfaces = load_surface_registry()
assert "python-backend" in surfaces
assert dependency_closure("desktop-macos", surfaces) == [
"python-backend",
"desktop-rust-backend",
"desktop-macos",
]


def test_namespaced_python_backend_metadata() -> None:
blessing = surface_blessing_from_metadata(
{
"blessed.python-backend": "true",
"blessed.python-backend.sha": "abc123",
"blessed.python-backend.at": "2026-07-07T00:00:00Z",
"blessed.python-backend.tier": "unit+contracts",
"blessed.python-backend.evidence": "backend-evidence.json",
},
"python-backend",
)
assert blessing.blessed
assert blessing.sha == "abc123"
assert blessing.blessed_at == "2026-07-07T00:00:00Z"
assert blessing.tier == "unit+contracts"
assert blessing.evidence == "backend-evidence.json"


def test_legacy_desktop_metadata_still_parses() -> None:
blessing = surface_blessing_from_metadata(
{
"blessed": "true",
"blessedSha": "desktop123",
"blessedAt": "2026-07-07T00:00:00Z",
"blessedTier": "2",
},
"desktop-macos",
allow_legacy_desktop=True,
)
assert blessing.blessed
assert blessing.sha == "desktop123"
assert blessing.blessed_at == "2026-07-07T00:00:00Z"
assert blessing.tier == "2"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.


def test_namespaced_desktop_metadata_preferred_over_legacy() -> None:
blessing = surface_blessing_from_metadata(
{
"blessed": "true",
"blessedSha": "stale",
"blessedAt": "2026-07-06T00:00:00Z",
"blessed.desktop-macos": "false",
"blessed.desktop-macos.sha": "current",
"blessed.desktop-macos.at": "2026-07-07T00:00:00Z",
},
"desktop-macos",
allow_legacy_desktop=True,
)
assert not blessing.blessed
assert blessing.sha == "current"
assert blessing.blessed_at == "2026-07-07T00:00:00Z"


def test_closure_requires_every_surface_blessed() -> None:
blessings = {
"python-backend": SurfaceBlessing("python-backend", True, "py123", "2026-07-07T00:00:00Z", "unit", ""),
"desktop-rust-backend": SurfaceBlessing(
"desktop-rust-backend",
True,
"rust123",
"2026-07-07T00:00:00Z",
"contracts",
"",
),
}
try:
require_blessed_closure("desktop-macos", blessings)
except SystemExit as exc:
assert "desktop-macos" in str(exc)
else:
raise AssertionError("missing desktop-macos blessing should fail")


def test_closure_rejects_mismatched_blessing_surface() -> None:
blessings = {
"python-backend": SurfaceBlessing("desktop-macos", True, "py123", "2026-07-07T00:00:00Z", "unit", ""),
"desktop-rust-backend": SurfaceBlessing(
"desktop-rust-backend",
True,
"rust123",
"2026-07-07T00:00:00Z",
"contracts",
"",
),
"desktop-macos": SurfaceBlessing("desktop-macos", True, "desk123", "2026-07-07T00:00:00Z", "2", ""),
}
try:
require_blessed_closure("desktop-macos", blessings)
except SystemExit as exc:
assert "desktop-macos promotion has desktop-macos blessing data for python-backend" in str(exc)
else:
raise AssertionError("mismatched blessing surface should fail")


if __name__ == "__main__":
test_registry_loads_and_orders_dependencies()
test_namespaced_python_backend_metadata()
test_legacy_desktop_metadata_still_parses()
test_namespaced_desktop_metadata_preferred_over_legacy()
test_closure_requires_every_surface_blessed()
test_closure_rejects_mismatched_blessing_surface()
print("release blessing tests OK")
Loading