From e79563d172acddbcfec1f310f71d3d07d9dd4c61 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:04:08 +0800 Subject: [PATCH 1/2] Add mirror invariant guardrails Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 21 ++++++++++++++ README.md | 3 ++ check_invariants.py | 59 ++++++++++++++++++++++++++++++++++++++++ sync.py | 12 +++++++- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 check_invariants.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a8217e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + pull_request: + workflow_dispatch: + +jobs: + guardrails: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Check mirrored metadata invariants + run: python check_invariants.py + + - name: Smoke install + run: python -m pip install . diff --git a/README.md b/README.md index 35db049..9343ca2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ references in this repository, creates the corresponding mirror commit, pushes tag `vX.Y.Z`, and opens a GitHub release that points back to the mirrored PyPI release. +Pull requests run a metadata invariant check plus a smoke install so version +drift is caught before release automation runs. + ## Usage ```yaml diff --git a/check_invariants.py b/check_invariants.py new file mode 100644 index 0000000..9eece98 --- /dev/null +++ b/check_invariants.py @@ -0,0 +1,59 @@ +"""Validate mirrored release metadata stays in lockstep.""" + +from __future__ import annotations + +import re +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +PYPROJECT = ROOT / "pyproject.toml" +README = ROOT / "README.md" + +README_REV_PATTERN = re.compile(r"(?m)^\s*rev: v(?P\d+\.\d+\.\d+)\s*$") + + +def validate_repo_state() -> None: + pyproject_version, dependency_pin = read_pyproject_versions() + if pyproject_version != dependency_pin: + raise RuntimeError( + "pyproject.toml version and spore-lang dependency pin must match " + f"({pyproject_version!r} != {dependency_pin!r})." + ) + + readme_version = read_readme_version() + if readme_version != pyproject_version: + raise RuntimeError( + "README.md rev must match the pyproject.toml version " + f"({readme_version!r} != {pyproject_version!r})." + ) + + +def read_pyproject_versions() -> tuple[str, str]: + with PYPROJECT.open("rb") as handle: + pyproject = tomllib.load(handle) + + project = pyproject["project"] + project_version = str(project["version"]) + dependency_pin = read_dependency_pin(project["dependencies"][0]) + return project_version, dependency_pin + + +def read_dependency_pin(requirement: str) -> str: + match = re.fullmatch(r"spore-lang==(?P\d+\.\d+\.\d+)", requirement) + if match is None: + raise RuntimeError( + "Expected pyproject.toml to pin spore-lang with an exact release version." + ) + return match.group("version") + + +def read_readme_version() -> str: + matches = README_REV_PATTERN.findall(README.read_text()) + if len(matches) != 1: + raise RuntimeError("README.md must contain exactly one mirrored rev line.") + return matches[0] + + +if __name__ == "__main__": + validate_repo_state() diff --git a/sync.py b/sync.py index bc79eac..f2bf558 100644 --- a/sync.py +++ b/sync.py @@ -16,6 +16,8 @@ from packaging.requirements import Requirement from packaging.version import Version +from check_invariants import validate_repo_state + ROOT = Path(__file__).resolve().parent PYPROJECT = ROOT / "pyproject.toml" README = ROOT / "README.md" @@ -37,6 +39,7 @@ def main() -> None: current_version = read_current_version() + validate_repo_state() maybe_publish_current_tag(current_version) versions = find_new_versions(current_version) if not versions: @@ -63,11 +66,18 @@ def read_current_version() -> Version: with PYPROJECT.open("rb") as handle: pyproject = tomllib.load(handle) + project_version = Version(pyproject["project"]["version"]) requirement = Requirement(pyproject["project"]["dependencies"][0]) specifier = next(iter(requirement.specifier), None) if specifier is None or specifier.operator != "==": raise RuntimeError(f"Expected an exact {PACKAGE_NAME} pin in pyproject.toml.") - return Version(specifier.version) + dependency_version = Version(specifier.version) + if project_version != dependency_version: + raise RuntimeError( + "pyproject.toml version and dependency pin must match " + f"({project_version} != {dependency_version})." + ) + return dependency_version def find_new_versions(current_version: Version) -> list[Version]: From 88c27a81447df4c72ddb29d889cd87d66470aa80 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:16:25 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A7=20chore:=20drop=20invariant=20?= =?UTF-8?q?test=20guardrails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 21 -------------- README.md | 3 -- check_invariants.py | 59 ---------------------------------------- sync.py | 12 +------- 4 files changed, 1 insertion(+), 94 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 check_invariants.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a8217e5..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: CI - -on: - pull_request: - workflow_dispatch: - -jobs: - guardrails: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Check mirrored metadata invariants - run: python check_invariants.py - - - name: Smoke install - run: python -m pip install . diff --git a/README.md b/README.md index 9343ca2..35db049 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,6 @@ references in this repository, creates the corresponding mirror commit, pushes tag `vX.Y.Z`, and opens a GitHub release that points back to the mirrored PyPI release. -Pull requests run a metadata invariant check plus a smoke install so version -drift is caught before release automation runs. - ## Usage ```yaml diff --git a/check_invariants.py b/check_invariants.py deleted file mode 100644 index 9eece98..0000000 --- a/check_invariants.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Validate mirrored release metadata stays in lockstep.""" - -from __future__ import annotations - -import re -import tomllib -from pathlib import Path - -ROOT = Path(__file__).resolve().parent -PYPROJECT = ROOT / "pyproject.toml" -README = ROOT / "README.md" - -README_REV_PATTERN = re.compile(r"(?m)^\s*rev: v(?P\d+\.\d+\.\d+)\s*$") - - -def validate_repo_state() -> None: - pyproject_version, dependency_pin = read_pyproject_versions() - if pyproject_version != dependency_pin: - raise RuntimeError( - "pyproject.toml version and spore-lang dependency pin must match " - f"({pyproject_version!r} != {dependency_pin!r})." - ) - - readme_version = read_readme_version() - if readme_version != pyproject_version: - raise RuntimeError( - "README.md rev must match the pyproject.toml version " - f"({readme_version!r} != {pyproject_version!r})." - ) - - -def read_pyproject_versions() -> tuple[str, str]: - with PYPROJECT.open("rb") as handle: - pyproject = tomllib.load(handle) - - project = pyproject["project"] - project_version = str(project["version"]) - dependency_pin = read_dependency_pin(project["dependencies"][0]) - return project_version, dependency_pin - - -def read_dependency_pin(requirement: str) -> str: - match = re.fullmatch(r"spore-lang==(?P\d+\.\d+\.\d+)", requirement) - if match is None: - raise RuntimeError( - "Expected pyproject.toml to pin spore-lang with an exact release version." - ) - return match.group("version") - - -def read_readme_version() -> str: - matches = README_REV_PATTERN.findall(README.read_text()) - if len(matches) != 1: - raise RuntimeError("README.md must contain exactly one mirrored rev line.") - return matches[0] - - -if __name__ == "__main__": - validate_repo_state() diff --git a/sync.py b/sync.py index f2bf558..bc79eac 100644 --- a/sync.py +++ b/sync.py @@ -16,8 +16,6 @@ from packaging.requirements import Requirement from packaging.version import Version -from check_invariants import validate_repo_state - ROOT = Path(__file__).resolve().parent PYPROJECT = ROOT / "pyproject.toml" README = ROOT / "README.md" @@ -39,7 +37,6 @@ def main() -> None: current_version = read_current_version() - validate_repo_state() maybe_publish_current_tag(current_version) versions = find_new_versions(current_version) if not versions: @@ -66,18 +63,11 @@ def read_current_version() -> Version: with PYPROJECT.open("rb") as handle: pyproject = tomllib.load(handle) - project_version = Version(pyproject["project"]["version"]) requirement = Requirement(pyproject["project"]["dependencies"][0]) specifier = next(iter(requirement.specifier), None) if specifier is None or specifier.operator != "==": raise RuntimeError(f"Expected an exact {PACKAGE_NAME} pin in pyproject.toml.") - dependency_version = Version(specifier.version) - if project_version != dependency_version: - raise RuntimeError( - "pyproject.toml version and dependency pin must match " - f"({project_version} != {dependency_version})." - ) - return dependency_version + return Version(specifier.version) def find_new_versions(current_version: Version) -> list[Version]: