#!/usr/bin/env python3 """Verify the version stamp a bootstrap installer ships. The bootstrap installers (scripts/install.sh, scripts/install.ps1) finish with a ``complete`` stage that writes ``.hermes-bootstrap-complete`` into the install dir: ``{"schemaVersion": 1, "pinnedCommit": <40-hex sha>, "pinnedBranch": , "completedAt": }``. That stamp is what update tooling and support triage read back, so it must tell the truth about the bytes actually checked out. This script reads a stamp back and cross-checks it against the installed checkout it describes: * ``schemaVersion`` is 1 * ``pinnedCommit`` is a full 40-char lowercase hex sha AND equals ``git rev-parse HEAD`` of the install repo (or ``--expect-commit``) * ``pinnedBranch`` equals the repo's checked-out branch (or ``--expect-branch``) * ``completedAt`` parses as an ISO-8601 UTC timestamp * the install carries its source identity stamp — ``install-stamp.json`` whose ``commit`` matches the installed checkout HEAD (``baseVersion`` is null when no release tag is reachable) Usage: python3 scripts/verify-bootstrap-version-stamp.py \ --stamp /.hermes-bootstrap-complete --repo # CI lane additionally pins the expected commit/branch: python3 scripts/verify-bootstrap-version-stamp.py --stamp ... --repo ... \ --expect-commit "$SHA" --expect-branch ci-under-test Exit 0 = the stamp tells the truth; exit 1 = any check fails (each failure prints one ``FAIL:`` line). """ from __future__ import annotations import argparse import json import os import re import shutil import subprocess import sys from datetime import datetime from pathlib import Path SHA_RE = re.compile(r"^[0-9a-f]{40}$") def _git_exe() -> str: """Resolve git to an executable CreateProcess can start. On sandboxed hosts PATH can resolve ``git`` to a store-app payload copy (e.g. under ``C:\\Program Files\\WindowsApps\\...``) that fails to exec outside its package context; prefer a conventional install there. """ candidates: list[str] = [] hit = shutil.which("git") if hit: candidates.append(hit) if sys.platform == "win32": base = Path(os.environ.get("ProgramFiles", r"C:\Program Files")) / "Git" for rel in (("cmd", "git.exe"), ("bin", "git.exe")): p = base.joinpath(*rel) if p.exists(): candidates.append(str(p)) for cand in candidates: if "windowsapps" not in cand.lower(): return cand return candidates[0] if candidates else "git" _GIT = _git_exe() def _fail(errors: list[str], message: str) -> None: errors.append(message) def _git(repo: Path, *args: str) -> str | None: try: result = subprocess.run( [_GIT, *args], cwd=str(repo), capture_output=True, text=True, timeout=15 ) except (OSError, subprocess.SubprocessError): return None value = (result.stdout or "").strip() return value if result.returncode == 0 and value else None def verify_stamp(stamp_path: Path, repo: Path, expect_commit: str | None, expect_branch: str | None, *, source_stamp: bool = True) -> list[str]: errors: list[str] = [] try: stamp = json.loads(stamp_path.read_text(encoding="utf-8-sig")) except OSError as e: _fail(errors, f"cannot read stamp {stamp_path}: {e}") return errors except ValueError as e: _fail(errors, f"stamp is not valid JSON: {e}") return errors if not isinstance(stamp, dict): _fail(errors, f"stamp top level is {type(stamp).__name__}, expected object") return errors if stamp.get("schemaVersion") != 1: _fail(errors, f"schemaVersion {stamp.get('schemaVersion')!r}, expected 1") commit = stamp.get("pinnedCommit") if not isinstance(commit, str) or not SHA_RE.match(commit): _fail(errors, f"pinnedCommit {commit!r} is not a full 40-char lowercase hex sha") commit = None branch = stamp.get("pinnedBranch") if not isinstance(branch, str) or not branch: _fail(errors, f"pinnedBranch {branch!r} is empty") branch = None completed = stamp.get("completedAt") if not isinstance(completed, str): _fail(errors, f"completedAt {completed!r} is missing") else: try: parsed = datetime.fromisoformat(completed.replace("Z", "+00:00")) if parsed.utcoffset() is None or parsed.utcoffset().total_seconds() != 0: _fail(errors, f"completedAt {completed!r} is not UTC") except ValueError: _fail(errors, f"completedAt {completed!r} does not parse as ISO-8601") # Cross-check the stamp against the checkout it claims to describe. head = _git(repo, "rev-parse", "HEAD") if commit is not None: if head is None: _fail(errors, f"could not read HEAD of {repo} to compare with pinnedCommit") elif head != commit: _fail(errors, f"pinnedCommit {commit[:12]} != installed HEAD {head[:12]}") if expect_commit and commit != expect_commit: _fail(errors, f"pinnedCommit {commit[:12]} != expected {expect_commit[:12]}") if branch is not None: actual = _git(repo, "rev-parse", "--abbrev-ref", "HEAD") if expect_branch: if branch != expect_branch: _fail(errors, f"pinnedBranch {branch!r} != expected {expect_branch!r}") elif actual and actual != "HEAD" and actual != branch: _fail(errors, f"pinnedBranch {branch!r} != checked-out branch {actual!r}") # A working install carries its source identity stamp (written by the # products stage), and it must tell the truth about the checkout: # commit == HEAD. baseVersion is null when no release tag is reachable # (a PR checkout), exactly as the runtime reports it. if source_stamp: present, canonical_commit = _read_install_stamp(repo) if not present: _fail(errors, f"no {repo}/install-stamp.json — the install carries no source identity stamp") elif not canonical_commit: _fail(errors, f"{repo}/install-stamp.json names no commit") elif head and canonical_commit != head: _fail(errors, f"canonical stamp commit {canonical_commit[:12]} != installed HEAD {head[:12]}") return errors def _read_install_stamp(repo: Path) -> tuple[bool, str | None]: """Read whether the INSTALL repo's install-stamp.json exists, and its commit.""" stamp_path = repo / "install-stamp.json" try: stamp = json.loads(stamp_path.read_text(encoding="utf-8-sig")) except (OSError, ValueError): return False, None if not isinstance(stamp, dict): return False, None commit = stamp.get("commit") return True, commit if isinstance(commit, str) and commit else None def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--stamp", required=True, help="Path to .hermes-bootstrap-complete") parser.add_argument("--repo", required=True, help="The install checkout the stamp describes") parser.add_argument("--expect-commit", default=None, help="Fail unless pinnedCommit equals this sha") parser.add_argument("--expect-branch", default=None, help="Fail unless pinnedBranch equals this branch") parser.add_argument("--no-source-stamp", action="store_true", help="The run skipped the products stage, which writes install-stamp.json") args = parser.parse_args() errors = verify_stamp( Path(args.stamp), Path(args.repo), args.expect_commit, args.expect_branch, source_stamp=not args.no_source_stamp, ) if errors: for e in errors: print(f"FAIL: {e}", file=sys.stderr) return 1 print("bootstrap version stamp verified") return 0 if __name__ == "__main__": raise SystemExit(main())