diff --git a/.github/scripts/verify-npm-hyperd-pin.py b/.github/scripts/verify-npm-hyperd-pin.py new file mode 100644 index 0000000..2803ec1 --- /dev/null +++ b/.github/scripts/verify-npm-hyperd-pin.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Guard: keep the npm release workflow's hyperd pin in sync with the toml. + +`.github/workflows/npm-build-publish.yml` bundles `hyperd` into the npm +packages using its OWN hardcoded `HYPERD_VERSION` / `HYPERD_BUILD_ID` / +per-platform matrix `hyperd-sha256`s. Those are decoupled from +`hyperdb-bootstrap/hyperd-version.toml`, which is what `make download-hyperd` +and the crates.io path use. + +When only the toml was bumped (as in PR #237), npm silently kept shipping the +old engine: 0.7.1 bundled hyperd 0.0.25080 while crates.io shipped 0.0.26359. +This script fails CI whenever the two drift, so that can't recur silently. + +The platform slug (`macos-arm64`, `linux-x86_64`, `windows-x86_64`) is the join +key: it is identical between the toml's `[sha256]` table and the workflow's +`hyperd-slug` matrix field. Only slugs the workflow actually builds are checked, +so a commented-out matrix entry (invisible to the YAML parser) and any unused +extra toml sha are both fine. +""" + +from __future__ import annotations + +import sys +import tomllib +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +TOML = ROOT / "hyperdb-bootstrap" / "hyperd-version.toml" +WORKFLOW = ROOT / ".github" / "workflows" / "npm-build-publish.yml" + + +def main() -> int: + toml_data = tomllib.loads(TOML.read_text()) + workflow = yaml.safe_load(WORKFLOW.read_text()) + + env = workflow.get("env", {}) + toml_sha = toml_data.get("sha256", {}) + + # (label, expected-from-toml, actual-from-workflow) + checks: list[tuple[str, str, str | None]] = [ + ("HYPERD_VERSION", str(toml_data["version"]), env.get("HYPERD_VERSION")), + ("HYPERD_BUILD_ID", str(toml_data["build_id"]), env.get("HYPERD_BUILD_ID")), + ] + + errors: list[str] = [] + + include = workflow["jobs"]["build-npm"]["strategy"]["matrix"]["include"] + for entry in include: + slug = entry.get("hyperd-slug") + if slug is None: + continue + expected = toml_sha.get(slug) + if expected is None: + errors.append( + f'matrix slug "{slug}" has no [sha256]."{slug}" entry in {TOML.name}' + ) + continue + checks.append((f"sha256[{slug}]", expected, entry.get("hyperd-sha256"))) + + for label, expected, actual in checks: + if actual == expected: + print(f"ok: {label} = {expected}") + else: + errors.append(f"{label}: workflow has {actual!r}, toml has {expected!r}") + + if errors: + print() + for err in errors: + print(f"::error::hyperd pin drift — {err}") + sys.stdout.flush() + print( + f"\n{WORKFLOW.name} is out of sync with {TOML.name}. " + "Update the workflow's env vars and matrix sha256s to match the toml " + "(or vice versa) so npm bundles the same hyperd as crates.io.", + file=sys.stderr, + ) + return 1 + + print(f"\n{WORKFLOW.name} hyperd pin matches {TOML.name}.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/npm-build-publish.yml b/.github/workflows/npm-build-publish.yml index b8f9d2b..ed8fffb 100644 --- a/.github/workflows/npm-build-publish.yml +++ b/.github/workflows/npm-build-publish.yml @@ -23,8 +23,8 @@ permissions: env: CARGO_TERM_COLOR: always - HYPERD_VERSION: "0.0.25080" - HYPERD_BUILD_ID: "r2bfd835b" + HYPERD_VERSION: "0.0.26359" + HYPERD_BUILD_ID: "r07abb490" jobs: verify-ci: @@ -96,7 +96,7 @@ jobs: os: macos-14 target: aarch64-apple-darwin hyperd-slug: macos-arm64 - hyperd-sha256: "2b0fa3fefcf4eba60f052e1cb51abfc32d8c84354274513763760f9549b45991" + hyperd-sha256: "434a5e7f95d914a7b328ac333b872fa044fabc6e23ad2aac9d0c9b762032b5ab" # TODO: re-enable when macos-13 runners are more available # - platform: darwin-x64 # os: macos-13 @@ -107,12 +107,12 @@ jobs: os: ubuntu-latest target: x86_64-unknown-linux-gnu hyperd-slug: linux-x86_64 - hyperd-sha256: "3d3fd2104f55f7fad832470592394dc78f350a03d52e89d36c5288b202dd0bc0" + hyperd-sha256: "40e488c01ddc1ecaa53123a88fcf4150161a5828e1c22b475dd73e1cd9cbbbed" - platform: win32-x64-msvc os: windows-latest target: x86_64-pc-windows-msvc hyperd-slug: windows-x86_64 - hyperd-sha256: "9dc4851d416e0e6e00f0367ee6b45fcd676e7ba3a110d4644e3bec871b9aa1de" + hyperd-sha256: "8546e67501ed3f15e97c9a0ed6a0dfc56fa9f070878bac435a7e5f7d1bfb6c99" runs-on: ${{ matrix.os }} defaults: run: diff --git a/.github/workflows/verify-hyperd-pin.yml b/.github/workflows/verify-hyperd-pin.yml index b728ecb..55ac396 100644 --- a/.github/workflows/verify-hyperd-pin.yml +++ b/.github/workflows/verify-hyperd-pin.yml @@ -11,11 +11,15 @@ on: - "hyperdb-bootstrap/hyperd-version.toml" - "hyperdb-bootstrap/src/**" - ".github/workflows/verify-hyperd-pin.yml" + - ".github/workflows/npm-build-publish.yml" + - ".github/scripts/verify-npm-hyperd-pin.py" pull_request: paths: - "hyperdb-bootstrap/hyperd-version.toml" - "hyperdb-bootstrap/src/**" - ".github/workflows/verify-hyperd-pin.yml" + - ".github/workflows/npm-build-publish.yml" + - ".github/scripts/verify-npm-hyperd-pin.py" schedule: # Weekly sanity check — independent of PR traffic. - cron: "0 12 * * 1" @@ -30,8 +34,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v7 - - name: Install mold linker - run: sudo apt-get update -q && sudo apt-get install -y mold + - name: Install build + guard dependencies (mold, python3-yaml) + run: sudo apt-get update -q && sudo apt-get install -y mold python3-yaml - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable @@ -39,3 +43,10 @@ jobs: rustflags: "" - name: Verify pinned release URLs are reachable run: cargo run --release -p hyperdb-bootstrap --bin hyperdb-bootstrap -- verify + + - name: Verify npm-build-publish.yml hyperd pin matches the toml + # The release workflow bundles hyperd into the npm packages from its + # OWN hardcoded version/build_id/sha256s, decoupled from the toml. + # 0.7.1 shipped npm with the stale 0.0.25080 engine because only the + # toml was bumped. This guard fails the build if they ever drift again. + run: python3 .github/scripts/verify-npm-hyperd-pin.py