From a8f59f5fc2454a35aac5aa938b4265681af750f4 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Tue, 28 Apr 2026 10:50:09 -0700 Subject: [PATCH] ci(pin-compat): split into two workflows so each gets a narrow paths filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #134. The post-merge review of #2196 flagged that the combined workflow's `paths:` filter (the union of both jobs' needs: `workspace/**` + `scripts/build_runtime_package.py` + the workflow itself) caused the `pypi-latest-install` job to fire on every doc-only / adapter-only / unrelated workspace/ edit. The PyPI artifact that job tests against can't change based on our workspace/ source — only on actual PyPI publishes — so those runs add noise without information. Splits the previously-merged combined workflow: runtime-pin-compat.yml (kept): - PyPI-latest install + import smoke (was: pypi-latest-install) - Narrow `paths:` filter — only fires when workspace/requirements.txt or this workflow file changes - Cron-driven daily for upstream-yank detection (unchanged) runtime-prbuild-compat.yml (new): - PR-built wheel + import smoke (was: local-build-install) - Broad `paths:` filter — fires on any workspace/ source change, scripts/build_runtime_package.py, or this workflow file - No cron (workspace/ doesn't change between firings) Behavior identical to before for content; only the trigger surface is narrower per-job. Each workflow's name is its own status check, so branch protection (which currently lists neither as required) can gate them independently in future. The prior comment in the combined file explicitly acknowledged the asymmetry and proposed this split as a follow-up; this is that follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/runtime-pin-compat.yml | 91 ++++------------- .github/workflows/runtime-prbuild-compat.yml | 100 +++++++++++++++++++ 2 files changed, 119 insertions(+), 72 deletions(-) create mode 100644 .github/workflows/runtime-prbuild-compat.yml diff --git a/.github/workflows/runtime-pin-compat.yml b/.github/workflows/runtime-pin-compat.yml index 283198da..2672f355 100644 --- a/.github/workflows/runtime-pin-compat.yml +++ b/.github/workflows/runtime-pin-compat.yml @@ -10,41 +10,35 @@ name: Runtime Pin Compatibility # 4. Every tenant workspace crashed; the canary tenant caught it but # only after 5 hours of degraded staging # -# Two jobs run independently: +# This workflow installs the CURRENTLY PUBLISHED runtime from PyPI on +# top of `workspace/requirements.txt` and smoke-imports. Catches: +# - Upstream PyPI yanks +# - Bad re-releases of molecule-ai-workspace-runtime +# - Already-shipped wheels that stop importing because a transitive +# dep moved underneath # -# - pypi-latest-install: installs the CURRENTLY PUBLISHED runtime from -# PyPI on top of workspace/requirements.txt. Catches upstream PyPI -# yanks, bad re-releases, and our own already-shipped wheel that -# stops importing because a transitive dep moved underneath. This is -# what the daily cron exercises. -# -# - local-build-install: builds a wheel from THIS PR's workspace/ -# directory using scripts/build_runtime_package.py (mirroring -# publish-runtime.yml exactly), installs that wheel, then imports. -# Closes the chicken-and-egg: a PR that adds a new import requiring -# a2a-sdk 1.5 would previously slip through (CI installs the OLD -# PyPI wheel that doesn't have the new import → smoke passes → -# merge → publish-runtime.yml ships the broken wheel → tenant -# images all crash on next boot). The local-build job tests the -# artifact that WOULD be published, not the artifact already on PyPI. +# This is the "PyPI artifact health" half of pin compatibility. The +# companion workflow `runtime-prbuild-compat.yml` covers the +# "PR-introduced breakage" half by building the wheel from THIS PR's +# workspace/ source. Splitting the two means each gets a narrow +# `paths:` filter — the pypi-latest job no longer fires on doc-only +# workspace/ edits whose content can't change what's currently on PyPI. on: push: branches: [main, staging] paths: - # pypi-latest job is sensitive only to pin/workflow changes — - # the upstream artifact doesn't change with workspace/ edits. - # local-build job is sensitive to anything that changes the wheel. - # Listing the union here means both jobs evaluate; each job's - # internal logic decides whether to do real work. - - 'workspace/**' - - 'scripts/build_runtime_package.py' + # Narrow filter: pypi-latest is sensitive only to changes that + # affect what we're INSTALLING (requirements.txt) or WHAT THE + # CHECK ITSELF DOES (this workflow file). Edits to workspace/ + # source code don't change what's on PyPI right now, so they + # don't change this gate's verdict. + - 'workspace/requirements.txt' - '.github/workflows/runtime-pin-compat.yml' pull_request: branches: [main, staging] paths: - - 'workspace/**' - - 'scripts/build_runtime_package.py' + - 'workspace/requirements.txt' - '.github/workflows/runtime-pin-compat.yml' # Daily catch for upstream PyPI publishes that break the pin combo # without any change in our repo (e.g. someone re-yanks an a2a-sdk @@ -95,50 +89,3 @@ jobs: WORKSPACE_ID: 00000000-0000-0000-0000-000000000001 run: | /tmp/venv/bin/python -c "from molecule_runtime.main import main_sync; print('runtime imports OK')" - - local-build-install: - # Builds the wheel from THIS PR's workspace/ + scripts/ and tests - # IT — the artifact that WOULD be published if this PR merges. The - # PyPI-latest job above only catches problems with the - # already-published artifact; this job catches problems introduced - # by the PR itself. - # - # No cron schedule: the same pre-merge run already covered the - # commit, and re-running daily wouldn't surface anything new - # (workspace/ doesn't change between cron firings unless a PR - # already passed this gate). - name: PR-built wheel + import smoke - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: pip - cache-dependency-path: workspace/requirements.txt - - name: Install build tooling - run: pip install build - - name: Build wheel from PR source (mirrors publish-runtime.yml) - # Use a fixed test version so the wheel filename is predictable. - # Doesn't reach PyPI — this build is local-only for the smoke. - # Keep the build invocation byte-identical with publish-runtime.yml's - # build step so we test the SAME artifact pipeline; if they drift, - # the gate stops catching what publish actually ships. - run: | - python scripts/build_runtime_package.py \ - --version "0.0.0.dev0+pin-compat" \ - --out /tmp/runtime-build - cd /tmp/runtime-build && python -m build - - name: Install built wheel + workspace requirements - run: | - python -m venv /tmp/venv-built - /tmp/venv-built/bin/pip install --upgrade pip - /tmp/venv-built/bin/pip install /tmp/runtime-build/dist/*.whl - /tmp/venv-built/bin/pip install -r workspace/requirements.txt - /tmp/venv-built/bin/pip show molecule-ai-workspace-runtime a2a-sdk \ - | grep -E '^(Name|Version):' - - name: Smoke import the PR-built wheel - env: - WORKSPACE_ID: 00000000-0000-0000-0000-000000000001 - run: | - /tmp/venv-built/bin/python -c "from molecule_runtime.main import main_sync; print('PR-built runtime imports OK')" diff --git a/.github/workflows/runtime-prbuild-compat.yml b/.github/workflows/runtime-prbuild-compat.yml new file mode 100644 index 00000000..41f8332a --- /dev/null +++ b/.github/workflows/runtime-prbuild-compat.yml @@ -0,0 +1,100 @@ +name: Runtime PR-Built Compatibility + +# Companion to `runtime-pin-compat.yml`. That workflow tests what's +# CURRENTLY PUBLISHED on PyPI; this workflow tests what WOULD BE +# PUBLISHED if THIS PR merges. +# +# Why two workflows: the chicken-and-egg #128 fix added a "PR-built +# wheel" job to the original runtime-pin-compat.yml, but both jobs +# shared a `paths:` filter that was the union of their needs +# (`workspace/**`). That meant the PyPI-latest job ran on every doc +# edit even though the upstream PyPI artifact can't change with our +# workspace/ source. Splitting the two means each gets a narrow +# `paths:` filter that matches the inputs it actually depends on. +# +# Catches the failure mode where a PR adds an import requiring a newer +# SDK than `workspace/requirements.txt` pins: +# 1. Pip resolves the existing PyPI wheel + the old SDK pin → smoke +# passes (it imports the OLD main.py from the wheel, not the PR's +# new main.py). +# 2. Merge → publish-runtime.yml ships a wheel WITH the new import. +# 3. Tenant images redeploy → all crash on first boot with +# ImportError. +# +# By building from the PR's source and smoke-importing THAT wheel, we +# fail at PR-time instead of after publish. + +on: + push: + branches: [main, staging] + paths: + # Broad filter: this workflow's verdict can change whenever any + # workspace/ source file changes (because the wheel we build is + # produced from those files), or when the build script itself + # changes (it controls the wheel layout). + - 'workspace/**' + - 'scripts/build_runtime_package.py' + - '.github/workflows/runtime-prbuild-compat.yml' + pull_request: + branches: [main, staging] + paths: + - 'workspace/**' + - 'scripts/build_runtime_package.py' + - '.github/workflows/runtime-prbuild-compat.yml' + workflow_dispatch: + # Required-check support: when this becomes a branch-protection gate, + # merge_group runs let the queue green-check this in addition to PRs. + merge_group: + types: [checks_requested] + # No cron: the same pre-merge run already covered the commit, and + # re-running daily wouldn't surface anything new (workspace/ doesn't + # change between cron firings unless a PR already passed this gate). + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + local-build-install: + # Builds the wheel from THIS PR's workspace/ + scripts/ and tests + # IT — the artifact that WOULD be published if this PR merges. + name: PR-built wheel + import smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: pip + cache-dependency-path: workspace/requirements.txt + - name: Install build tooling + run: pip install build + - name: Build wheel from PR source (mirrors publish-runtime.yml) + # Use a fixed test version so the wheel filename is predictable. + # Doesn't reach PyPI — this build is local-only for the smoke. + # Use the SAME build script with the SAME args as + # publish-runtime.yml's build step. The temp dir path differs + # (`/tmp/runtime-build` here vs `${{ runner.temp }}/runtime-build` + # in publish-runtime.yml — they coincide on ubuntu-latest but + # the call sites are not byte-identical). The smoke import is + # also intentionally narrower than publish's: this gate exists + # to catch SDK-version-import drift specifically; full invariant + # coverage lives in publish-runtime.yml's own pre-PyPI smoke. + run: | + python scripts/build_runtime_package.py \ + --version "0.0.0.dev0+pin-compat" \ + --out /tmp/runtime-build + cd /tmp/runtime-build && python -m build + - name: Install built wheel + workspace requirements + run: | + python -m venv /tmp/venv-built + /tmp/venv-built/bin/pip install --upgrade pip + /tmp/venv-built/bin/pip install /tmp/runtime-build/dist/*.whl + /tmp/venv-built/bin/pip install -r workspace/requirements.txt + /tmp/venv-built/bin/pip show molecule-ai-workspace-runtime a2a-sdk \ + | grep -E '^(Name|Version):' + - name: Smoke import the PR-built wheel + env: + WORKSPACE_ID: 00000000-0000-0000-0000-000000000001 + run: | + /tmp/venv-built/bin/python -c "from molecule_runtime.main import main_sync; print('PR-built runtime imports OK')"