From 2cf042f93c469a90692641adcd32abc01099d7ac Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sun, 25 Oct 2026 11:47:20 -0700 Subject: [PATCH] Stop CETLVaSt from verifying the same commit twice A full CETLVaSt run is 21 jobs and 260-370 job-minutes. Of the 16,405 job-minutes the last 100 runs used, 2,726 went to verifying a commit a second time and 4,185 to pull request runs that kept going after a newer commit was pushed. - Version tags no longer trigger the workflow. The release script waits for the main push run of the tagged commit and packages that run's bundle; nothing read the tag run. - The #verification and #docs commit-message triggers are gone. On a branch with an open pull request they ran the full matrix in the push run as well as the pull_request run. A manual dispatch verifies a branch without a pull request. - Full verification runs for pull requests, dispatches, and pushes to refs/heads/main, matched exactly. contains(github.ref, '/main') matched any ref containing /main, and pushes to an issue/* branch with an open pull request ran the matrix on both events. - A new commit cancels the running pull_request verification of its pull request. Runs on main are never cancelled because the release waits for them. - cache-warmup needs release-tests, so a release tooling failure stops the matrix instead of running it to a release-ready that must fail. The release contract tests follow the new conditions, and a new test keeps version tags out of the push trigger. CONTRIBUTING.md describes the triggers. Closes #267. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/cetlvast.yml | 53 ++++++++++++---------------------- CONTRIBUTING.md | 23 +++++++-------- scripts/tests/test_release.py | 11 +++++-- 3 files changed, 37 insertions(+), 50 deletions(-) diff --git a/.github/workflows/cetlvast.yml b/.github/workflows/cetlvast.yml index 419f247..b4e6447 100644 --- a/.github/workflows/cetlvast.yml +++ b/.github/workflows/cetlvast.yml @@ -6,10 +6,13 @@ permissions: concurrency: group: cetlvast-${{ github.ref }} - cancel-in-progress: false + # A newer commit supersedes a pull request run. Runs on main are never cancelled: the release waits for them. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: - push: # Further filtering is done in the jobs. + push: + # Tags are excluded: a release reuses the main run of its commit. Further filtering is done in the jobs. + branches: ["**"] pull_request: branches: - main @@ -47,16 +50,12 @@ jobs: cache-warmup: if: > - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.event.head_commit.message, '#docs') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - (github.event_name == 'pull_request') + github.ref == 'refs/heads/main' runs-on: ubuntu-latest container: ghcr.io/opencyphal/toolshed:ts26.4.5 - needs: [version] + needs: [version, release-tests] steps: - uses: actions/checkout@v7 with: @@ -74,12 +73,9 @@ jobs: verification-amd64: if: > - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - (github.event_name == 'pull_request') + github.ref == 'refs/heads/main' runs-on: ubuntu-latest container: ghcr.io/opencyphal/toolshed:ts26.4.5 needs: @@ -117,12 +113,9 @@ jobs: verification-arm64: if: > - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - (github.event_name == 'pull_request') + github.ref == 'refs/heads/main' runs-on: ubuntu-24.04-arm container: ghcr.io/opencyphal/toolshed:ts26.4.5 needs: @@ -161,12 +154,9 @@ jobs: # Compilers the current toolshed image no longer ships run here, in the last image that has them. verification-legacy-arm64: if: > - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - (github.event_name == 'pull_request') + github.ref == 'refs/heads/main' runs-on: ubuntu-24.04-arm # ts26.4.x dropped g++-7. Keep this pinned to ts24.4.3 rather than following the other jobs. container: ghcr.io/opencyphal/toolshed:ts24.4.3 @@ -206,13 +196,9 @@ jobs: docs: if: > - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.event.head_commit.message, '#docs') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - (github.event_name == 'pull_request') + github.ref == 'refs/heads/main' runs-on: ubuntu-latest container: ghcr.io/opencyphal/toolshed:ts26.4.5 needs: @@ -254,12 +240,9 @@ jobs: release-ready: if: >- always() && ( - startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || - contains(github.event.head_commit.message, '#verification') || - contains(github.ref, '/main') || - contains(github.ref, '/issue/') || - github.event_name == 'pull_request') + github.ref == 'refs/heads/main') runs-on: ubuntu-latest needs: [version, release-tests, cache-warmup, verification-amd64, verification-arm64, verification-legacy-arm64, docs] steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3acaba6..a40e470 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,23 +164,20 @@ invoke Clang-Format of the correct version from the container (be sure to use th docker run --rm -v ${PWD}:/repo ghcr.io/opencyphal/toolshed:ts26.4.5 /bin/sh -c 'cd cetlvast && cmake --preset configure-clang-native-cpp-17-offline && cd build && ninja danger-danger-cetlvast-clang-format-in-place' ``` -# `issue/*` and hashtag-based CI triggering +# CI triggering -Normally, the CI will only run on pull requests (PR), releases, and perhaps some other special occasions on `main` branch. -Often, however, you will want to run it on your branch before proposing the changes to ensure all checks are -green and test coverage is adequate - to do that: +Every push to a branch runs version/header validation and the release-tool tests. Full verification, including +documentation and release archive checks, runs for pull requests (PR) into `main` or an `issue/*` branch, for pushes +to `main`, and for manual dispatches. A PR in a GitHub stack gets full verification as if it targeted the base of +the stack. To verify a branch before proposing the changes to `main`: - either target your PR to any `issue/NN_LABEL` branch, where `NN` is the issue number and `LABEL` is a small title giving context (like `issue/83_any`) -- or add a hashtag with the name of the workflow you need to run to the head commit; -for example, making a commit with a message like `Add feature such and such #verification #docs` -will force the CI to execute jobs named `verification` and `docs`. +- or, with write access, dispatch the workflow on the branch: `gh workflow run cetlvast.yml --ref `. -Note that if the job you requested is dependent on other jobs that are not triggered, it will not run. +Do not dispatch for a commit that a PR run already verifies; each full run is expensive. -Version/header validation and release-tool tests run independently of hashtags. -The `#verification` trigger includes documentation and release archive checks. -Version tags and verification dispatches run full verification. Documentation -deployment verifies the published bundle and its successful main CI run, then -deploys the packaged documentation. +A new commit cancels the running verification of the PR it is pushed to. Runs on `main` are never cancelled. +A release reuses the `main` run of the tagged commit; pushing the version tag starts no CI. Documentation +deployment verifies the published bundle and its successful main CI run, then deploys the packaged documentation. See [Releasing CETL](RELEASING.md) for the maintainer Python CLI and recovery instructions. The CMake `release` target is a legacy build/test alias and does not publish a release. diff --git a/scripts/tests/test_release.py b/scripts/tests/test_release.py index 9e4623b..b6f9ddd 100644 --- a/scripts/tests/test_release.py +++ b/scripts/tests/test_release.py @@ -148,7 +148,6 @@ def test_matrix_and_gate_contract_matches_workflow(self): expected = {f"verification-{architecture} ({compiler}, {standard})" for compiler in compilers for standard in standards} self.assertEqual(expected, {name for name in required_jobs() if name.startswith(f"verification-{architecture} (")}) - self.assertIn("startsWith(github.ref, 'refs/tags/v')", section) self.assertNotIn("!inputs.docs_ref", section) gate = _workflow_group(r"^ release-ready:\n(.*?)(?=^ [a-z])", workflow, re.M | re.S) dependencies = _workflow_group(r"needs: \[(.*)\]", gate).split(", ") @@ -162,7 +161,7 @@ def test_full_release_gate_triggers_cover_all_dependencies(self): gate = _workflow_group(r"^ release-ready:\n(.*?)(?=^ [a-z])", workflow, re.M | re.S) condition_pattern = r"^ if: >-?\n(.*?)(?=^ \S)" # Each predicate is an OR term; always() lets the gate inspect failed dependencies. - predicate_pattern = r"(?:startsWith|contains)\([^()\n]*\)|github\.event_name == '[^']+'" + predicate_pattern = r"(?:startsWith|contains)\([^()\n]*\)|github\.(?:event_name|ref) == '[^']+'" condition = _workflow_group(condition_pattern, gate, re.M | re.S) gate_triggers = set(re.findall(predicate_pattern, condition)) self.assertTrue(gate_triggers) @@ -177,6 +176,14 @@ def test_full_release_gate_triggers_cover_all_dependencies(self): missing = gate_triggers - set(re.findall(predicate_pattern, condition)) self.assertFalse(missing, f"{dependency} skips full-gate triggers: {sorted(missing)}") + def test_version_tags_reuse_the_main_run(self): + """Leave version tags out of CETLVaSt; the release reuses the tagged commit's main push run.""" + workflow = (SCRIPTS.parent / ".github/workflows/cetlvast.yml").read_text() + push = _workflow_group(r"^ push:\n(.*?)(?=^ [a-z])", workflow, re.M | re.S) + # A push filter with only branches never matches a tag. + self.assertEqual(re.findall(r"^ ([a-z-]+):", push, re.M), ["branches"]) + self.assertNotIn("refs/tags", workflow) + class GitHubTransportTests(unittest.TestCase): """Check CLI/API encoding, local prerequisites, diagnostics, and public HTML reads."""