From 045d6cba2b4a131b02dad5f25ea811357359f5a6 Mon Sep 17 00:00:00 2001 From: Alexander Fengler Date: Wed, 5 Aug 2026 20:50:44 -0400 Subject: [PATCH 1/2] ci: scheduled drift detection workflow (drift.yml) - weekly re-run of the full test workflow against a fresh dependency resolve of unchanged main; this repo has NO push-to-main CI, so between PRs nothing else proves main still works - failures file ONE deduped drift-labeled issue; green runs close it - run_tests.yml gains workflow_call; setup-uv unpinned from the ancient 0.6.5; the per-PR cache-nuking block removed (every run cold- downloaded the multi-GB torch/bayesflow/keras/sbi stack) with uv caching enabled instead --- .github/workflows/drift.yml | 78 +++++++++++++++++++++++++++++++++ .github/workflows/run_tests.yml | 25 +++++------ 2 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/drift.yml diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml new file mode 100644 index 0000000..baca05a --- /dev/null +++ b/.github/workflows/drift.yml @@ -0,0 +1,78 @@ +name: Drift + +# Scheduled drift detection: re-run the full test workflow (unit matrix + +# notebook execution) against a fresh dependency resolve of an unchanged +# main. No lockfile is committed AND this repo has no push-to-main CI at +# all, so between PRs nothing else proves main still works. +# Failures file ONE deduped `drift`-labeled issue; green runs close it. +on: + schedule: + - cron: "20 5 * * 1" # weekly + workflow_dispatch: + inputs: + force_fail: + description: "Exercise the drift-issue path without a real failure" + type: boolean + default: false + +permissions: + contents: read + issues: write + +jobs: + tests: + uses: ./.github/workflows/run_tests.yml + + report: + needs: [tests] + if: always() + runs-on: ubuntu-latest + steps: + - name: Create, update, or close the deduped drift issue + env: + GH_TOKEN: ${{ github.token }} + RESULT_TESTS: ${{ needs.tests.result }} + FORCE_FAIL: ${{ inputs.force_fail }} + run: | + set -euo pipefail + KEY="drift-key: LANfactory-scheduled" + TITLE="drift: LANfactory scheduled checks failing" + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + + FAILED_JOBS="" + if [ "$RESULT_TESTS" = "failure" ]; then + FAILED_JOBS=" tests" + fi + if [ "$FORCE_FAIL" = "true" ]; then + FAILED_JOBS="$FAILED_JOBS (force_fail rehearsal)" + fi + + EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --label drift \ + --state open --search "in:body \"$KEY\"" \ + --json number --jq '.[0].number // empty') + + if [ -z "$FAILED_JOBS" ]; then + if [ -n "$EXISTING" ]; then + gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" \ + --comment "Scheduled drift checks green again: $RUN_URL" + fi + exit 0 + fi + + if [ -n "$EXISTING" ]; then + gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" \ + --body "Still failing (${FAILED_JOBS# }): $RUN_URL" + else + gh label create drift --repo "$GITHUB_REPOSITORY" \ + --description "scheduled drift detection failure" \ + --force 2>/dev/null || true + BODY=$(printf '%s\n' \ + "" \ + "Scheduled drift run failed in:${FAILED_JOBS}" \ + "Run: $RUN_URL" \ + "" \ + "This re-runs the existing test workflow against a fresh dependency resolve of an unchanged main — a failure usually means an upstream or toolchain release, not a code change." \ + "Triage via the spine's audit-drift skill.") + gh issue create --repo "$GITHUB_REPOSITORY" --label drift \ + --title "$TITLE" --body "$BODY" + fi diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 23e1d2a..f8602a6 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -2,6 +2,7 @@ name: Run tests on: pull_request: + workflow_call: # Called by drift.yml (scheduled drift detection) jobs: run_tests: @@ -27,22 +28,16 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "0.6.5" - # enable-cache: true - # cache-dependency-glob: "pyproject.toml pdm.lock" - - - name: Clear all caches - run: | - rm -rf ~/.cache/pip - rm -rf ~/.cache/uv - rm -rf ~/.cache/conda - rm -rf ~/.cache/npm - + version: "latest" + enable-cache: true + cache-dependency-glob: "pyproject.toml" + + # (a cache-nuking block used to sit here: rm -rf of every cache plus + # `uv sync --reinstall` with PIP_NO_CACHE_DIR — every PR run cold- + # downloaded the multi-GB torch/bayesflow/keras/sbi stack, and uv was + # pinned to the ancient 0.6.5) - name: Install package - run: uv sync --all-groups --reinstall - env: - # UV_CACHE_DIR: "" - PIP_NO_CACHE_DIR: "1" + run: uv sync --all-groups - name: Run pytest run: uv run pytest From bbc02ff06a75c6aa65db43090535592865ef47d1 Mon Sep 17 00:00:00 2001 From: Alexander Fengler Date: Sat, 8 Aug 2026 19:07:22 -0400 Subject: [PATCH 2/2] drift.yml: don't treat a cancelled run as green; unpin uv in test_notebooks Addresses the review on #107. Co-Authored-By: Claude Fable 5 --- .github/workflows/drift.yml | 28 ++++++++++++++++++++++++---- .github/workflows/run_tests.yml | 9 +++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index baca05a..177d297 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -17,16 +17,27 @@ on: permissions: contents: read - issues: write jobs: tests: + # Only `report` needs issue-write; the token that runs freshly-resolved + # third-party test code should not carry it. + permissions: + contents: read uses: ./.github/workflows/run_tests.yml report: needs: [tests] if: always() runs-on: ubuntu-latest + permissions: + contents: read + issues: write + # One issue reconciler at a time: the read at `gh issue list` and the write + # below are not atomic, so an overlapping dispatch could file a duplicate. + concurrency: + group: drift-issue-${{ github.repository }} + cancel-in-progress: false steps: - name: Create, update, or close the deduped drift issue env: @@ -51,10 +62,19 @@ jobs: --state open --search "in:body \"$KEY\"" \ --json number --jq '.[0].number // empty') + # Only a genuine success closes the issue. `report` runs under + # `if: always()`, which includes cancellation, and a cancelled or + # never-started test job is no evidence of health — treating it as + # green would silently retire a real drift issue. if [ -z "$FAILED_JOBS" ]; then - if [ -n "$EXISTING" ]; then - gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" \ - --comment "Scheduled drift checks green again: $RUN_URL" + if [ "$RESULT_TESTS" = "success" ]; then + if [ -n "$EXISTING" ]; then + gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" \ + --comment "Scheduled drift checks green again: $RUN_URL" + fi + else + echo "::notice::Inconclusive run (tests: $RESULT_TESTS) —" \ + "drift issue state left unchanged." fi exit 0 fi diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index f8602a6..5d0b27f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -35,7 +35,10 @@ jobs: # (a cache-nuking block used to sit here: rm -rf of every cache plus # `uv sync --reinstall` with PIP_NO_CACHE_DIR — every PR run cold- # downloaded the multi-GB torch/bayesflow/keras/sbi stack, and uv was - # pinned to the ancient 0.6.5) + # pinned to the ancient 0.6.5. It was added during an undiagnosed hour + # of trial and error in June 2025; the SIGILL it was flailing at came + # from ssm-simulators' `-march=native` wheels and is pinned out by + # `ssm-simulators>=0.13.1`. Don't reinstate it on a hunch.) - name: Install package run: uv sync --all-groups @@ -70,7 +73,9 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "0.6.5" + version: "latest" + enable-cache: true + cache-dependency-glob: "pyproject.toml" - name: Install package (with notebook + backend deps) run: uv sync --all-groups