From dd355d502d2ad4ddbee7d5ac7f2f1428ec1e308b Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:12:16 +0200 Subject: [PATCH 1/8] ci: make PR workflow canonical required gate --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0d205a4..aefc8b14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,11 @@ name: CI on: - push: - branches: - - main pull_request: - types: + branches: + - next + - main + types: - opened - synchronize - ready_for_review @@ -25,7 +25,7 @@ jobs: strategy: matrix: - node-version: [22, 24] + node-version: [22] steps: - name: Checkout code @@ -49,7 +49,7 @@ jobs: lint: strategy: matrix: - node-version: [22, 24] + node-version: [22] name: Code checks on node v${{ matrix.node-version }} runs-on: ubuntu-latest From be9eecacec3b5375c1137f3b496ca113552ebae1 Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:21:14 +0200 Subject: [PATCH 2/8] ci: add lightweight post-merge sentinel workflow --- .github/workflows/ci-post-merge.yml | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/ci-post-merge.yml diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml new file mode 100644 index 00000000..b5c0d4af --- /dev/null +++ b/.github/workflows/ci-post-merge.yml @@ -0,0 +1,38 @@ +name: CI Post-merge Sentinel + +on: + push: + branches: + - next + - main + +permissions: + contents: read + +concurrency: + group: post-merge-${{ github.ref }} + cancel-in-progress: true + +jobs: + sentinel: + name: Post-merge sentinel (node v22) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup node v22 + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install deps + run: npm ci + + - name: Build project + run: npm run build + + - name: Verify packed binaries + run: npm run verify:packed-binaries From 72aafc367312a658994da5f0633ede8774ac71ac Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:29:58 +0200 Subject: [PATCH 3/8] ci(release): publish on next/main push without schedule --- .github/workflows/release-check.yml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 8925a6af..fe256f0a 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -4,8 +4,7 @@ on: push: branches: - next - schedule: - - cron: "0 9 * * 1" + - main workflow_dispatch: permissions: @@ -13,7 +12,7 @@ permissions: id-token: write concurrency: - group: release-${{ github.event_name == 'schedule' && 'main' || github.ref_name }} + group: release-${{ github.ref_name }} cancel-in-progress: false jobs: @@ -50,11 +49,7 @@ jobs: id: target shell: bash run: | - if [ "${{ github.event_name }}" = "schedule" ]; then - branch="main" - else - branch="${{ github.ref_name }}" - fi + branch="${{ github.ref_name }}" case "$branch" in main|next) ;; @@ -114,15 +109,6 @@ jobs: - name: Build run: npm run build - - name: Unit tests - run: npm test - - - name: Lint and format check - run: npm run check:ci - - - name: Type check - run: npx tsc --noEmit - - name: Verify npm auth env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 18ba2fe0fc85f3e3bc57ccf0351b9e0b3d410e6e Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:38:12 +0200 Subject: [PATCH 4/8] ci(promote): trigger promotion only on next updates --- .github/workflows/promote-next-to-main.yml | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/promote-next-to-main.yml b/.github/workflows/promote-next-to-main.yml index cf2f2174..2223f6e5 100644 --- a/.github/workflows/promote-next-to-main.yml +++ b/.github/workflows/promote-next-to-main.yml @@ -4,9 +4,6 @@ on: push: branches: - next - release: - types: - - published workflow_dispatch: permissions: @@ -19,7 +16,6 @@ concurrency: jobs: promote: - if: ${{ github.event_name != 'release' || (github.event.release.prerelease == true && github.event.release.target_commitish == 'next') }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -31,7 +27,24 @@ jobs: - name: Fetch main and next refs run: git fetch origin main next + - name: No-op when next has no commits ahead of main + id: commits-check + shell: bash + run: | + set -euo pipefail + + commit_ahead=$(git rev-list --max-count=1 origin/main..origin/next) + + if [ -z "$commit_ahead" ]; then + echo "No commits to promote (origin/main..origin/next is empty)." + echo "has_commits=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_commits=true" >> "$GITHUB_OUTPUT" + - name: Generate linearis-bot app token + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} id: app-token uses: actions/create-github-app-token@v2 with: @@ -39,6 +52,7 @@ jobs: private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Build PR metadata + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} id: meta env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -112,6 +126,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Upsert promotion PR + if: ${{ steps.commits-check.outputs.has_commits == 'true' }} env: GH_TOKEN: ${{ steps.app-token.outputs.token }} PROMOTION_PR_TITLE: ${{ steps.meta.outputs.title }} From 8108b551747f64051267c1a86e4ffcacdec4aa72 Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:44:47 +0200 Subject: [PATCH 5/8] docs(ci): document new run model and required checks --- CONTRIBUTING.md | 7 +++-- README.md | 6 +++- docs/ci-run-model.md | 65 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 docs/ci-run-model.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36903743..493b320e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,12 +46,15 @@ Integration tests (`tests/integration/`) require `LINEAR_API_TOKEN` in your envi Publishing is fully automated by the `Release` workflow (`.github/workflows/release-check.yml`). - `next` (default branch): releases run on every push (prerelease channel) -- `main` (stable branch): releases run on weekly schedule +- `main` (stable branch): releases run on every push (stable channel) - Can be started manually with `workflow_dispatch` for either branch -- Executes build/test/lint/type-check gates before release +- No scheduled release run is configured +- Release workflow is lean and depends on PR required checks for quality gates - Uses semantic-release to decide if a release is required - Publishes npm package, creates tag, and creates GitHub release when releasable commits exist +Required check names for the repository ruleset are documented in [`docs/ci-run-model.md`](docs/ci-run-model.md). + ### Changelog ownership `CHANGELOG.md` is release-workflow-owned. Do not edit it in feature/fix PRs. diff --git a/README.md b/README.md index 65987588..1098c89f 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,14 @@ Add this (or a version adapted to your workflow) to your `AGENTS.md` or `CLAUDE. Releases are automated by GitHub Actions via `.github/workflows/release-check.yml`. - `next` (default branch): release runs on every push (prerelease channel) -- `main` (stable branch): release runs on weekly schedule +- `main` (stable branch): release runs on every push (stable channel) - Manual `workflow_dispatch` supports releasing from either `main` or `next` +- No scheduled release run is configured +- Release workflow is lean and relies on PR required checks for quality gates - `CHANGELOG.md` is automation-owned and must not be edited in pull requests +Required PR checks are documented in [`docs/ci-run-model.md`](docs/ci-run-model.md). + If a pull request branch contains `CHANGELOG.md` changes anywhere in `main...HEAD` history, CI fails and posts rebase instructions. ## Contributing diff --git a/docs/ci-run-model.md b/docs/ci-run-model.md new file mode 100644 index 00000000..935426ea --- /dev/null +++ b/docs/ci-run-model.md @@ -0,0 +1,65 @@ +# CI Run Model + +This document defines how pull request checks and release runs are wired together. + +## Workflow purpose + +| Workflow | Purpose | +| --- | --- | +| `ci.yml` | Runs required pull request checks used by branch protection/rulesets. | +| `release-check.yml` | Performs semantic-release on release branches after required checks have already passed in PR. | + +## Trigger matrix + +| Event | Branch | `ci.yml` | `release-check.yml` | +| --- | --- | --- | --- | +| `pull_request` | `main`, `next` | ✅ required checks | ❌ | +| `push` | `main`, `next` | ✅ sanity verification | ✅ release run | +| `workflow_dispatch` | selected ref | optional manual CI run | ✅ manual release run | + +## Required checks for repository ruleset + +The repository ruleset must require exactly these check names: + +- Unit tests on node v22 +- Code checks on node v22 + +These checks are produced by `ci.yml` and must be green before merging to `main` or `next`. + +## Release model (no schedule) + +Release workflow is lean and intentionally does not run a weekly schedule: + +- Push to `next` → prerelease channel +- Push to `main` → stable release channel +- Manual `workflow_dispatch` → ad-hoc release from selected ref + +Because release runs happen only after merges, quality gates live in PR required checks, not duplicated release-time full matrices. + +## Operational verification + +Use GitHub CLI to confirm the run model: + +```bash +# CI required checks from PRs + +gh run list --workflow ci.yml --event pull_request --limit 20 + +# Release runs on push (main + next) + +gh run list --workflow release-check.yml --event push --limit 20 + +# Optional manual release runs + +gh run list --workflow release-check.yml --event workflow_dispatch --limit 20 +``` + +## Rollback guidance + +If the model needs to be reverted quickly: + +1. Revert the workflow/ruleset refactor commit. +2. Confirm ruleset required checks point to existing check names. +3. Re-run a PR and confirm both required checks appear and pass. +4. Verify push-based release on `next` and `main` with `gh run list` filters above. +5. If release is blocked, run `workflow_dispatch` once as a temporary bridge while fixing ruleset/workflow drift. From f1f12aa8b67d99f87f90e0afc65ee35772d0059e Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:54:33 +0200 Subject: [PATCH 6/8] docs(ci): correct workflow trigger matrix and verification commands --- CONTRIBUTING.md | 16 +++++++--------- README.md | 15 ++++++--------- docs/ci-run-model.md | 19 ++++++++++++++----- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 493b320e..4fbdf995 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,17 +43,15 @@ Integration tests (`tests/integration/`) require `LINEAR_API_TOKEN` in your envi ## Publishing -Publishing is fully automated by the `Release` workflow (`.github/workflows/release-check.yml`). +Publishing is automated by GitHub Actions, primarily via the `Release` workflow (`.github/workflows/release-check.yml`). -- `next` (default branch): releases run on every push (prerelease channel) -- `main` (stable branch): releases run on every push (stable channel) -- Can be started manually with `workflow_dispatch` for either branch -- No scheduled release run is configured -- Release workflow is lean and depends on PR required checks for quality gates -- Uses semantic-release to decide if a release is required -- Publishes npm package, creates tag, and creates GitHub release when releasable commits exist +At a high level: -Required check names for the repository ruleset are documented in [`docs/ci-run-model.md`](docs/ci-run-model.md). +- PR quality gates run in `ci.yml` +- Post-merge sentinel validation runs in `ci-post-merge.yml` +- Releases run in `release-check.yml` (semantic-release decides whether to publish) + +For the authoritative workflow trigger matrix, required check names, and verification commands, see [`docs/ci-run-model.md`](docs/ci-run-model.md) (source of truth). ### Changelog ownership diff --git a/README.md b/README.md index 1098c89f..f5586e96 100644 --- a/README.md +++ b/README.md @@ -125,18 +125,15 @@ Add this (or a version adapted to your workflow) to your `AGENTS.md` or `CLAUDE. ## Release Automation Policy -Releases are automated by GitHub Actions via `.github/workflows/release-check.yml`. +Linearis uses three CI/release workflows: -- `next` (default branch): release runs on every push (prerelease channel) -- `main` (stable branch): release runs on every push (stable channel) -- Manual `workflow_dispatch` supports releasing from either `main` or `next` -- No scheduled release run is configured -- Release workflow is lean and relies on PR required checks for quality gates -- `CHANGELOG.md` is automation-owned and must not be edited in pull requests +- `ci.yml` for required pull request checks +- `ci-post-merge.yml` for post-merge sentinel validation on `main`/`next` pushes +- `release-check.yml` for push-driven and manual releases -Required PR checks are documented in [`docs/ci-run-model.md`](docs/ci-run-model.md). +For the authoritative trigger matrix, required checks, and operational verification commands, see [`docs/ci-run-model.md`](docs/ci-run-model.md) (source of truth). -If a pull request branch contains `CHANGELOG.md` changes anywhere in `main...HEAD` history, CI fails and posts rebase instructions. +`CHANGELOG.md` is automation-owned and must not be edited in pull requests. If a pull request branch contains `CHANGELOG.md` changes anywhere in `main...HEAD` history, CI fails and posts rebase instructions. ## Contributing diff --git a/docs/ci-run-model.md b/docs/ci-run-model.md index 935426ea..764dde5a 100644 --- a/docs/ci-run-model.md +++ b/docs/ci-run-model.md @@ -7,15 +7,16 @@ This document defines how pull request checks and release runs are wired togethe | Workflow | Purpose | | --- | --- | | `ci.yml` | Runs required pull request checks used by branch protection/rulesets. | +| `ci-post-merge.yml` | Runs post-merge sentinel validation on `main`/`next` pushes. | | `release-check.yml` | Performs semantic-release on release branches after required checks have already passed in PR. | ## Trigger matrix -| Event | Branch | `ci.yml` | `release-check.yml` | -| --- | --- | --- | --- | -| `pull_request` | `main`, `next` | ✅ required checks | ❌ | -| `push` | `main`, `next` | ✅ sanity verification | ✅ release run | -| `workflow_dispatch` | selected ref | optional manual CI run | ✅ manual release run | +| Event | Branch | `ci.yml` | `ci-post-merge.yml` | `release-check.yml` | +| --- | --- | --- | --- | --- | +| `pull_request` | `main`, `next` | ✅ required checks | ❌ | ❌ | +| `push` | `main`, `next` | ❌ | ✅ post-merge sentinel | ✅ release run | +| `workflow_dispatch` | selected ref | ❌ | ❌ | ✅ manual release run | ## Required checks for repository ruleset @@ -45,6 +46,14 @@ Use GitHub CLI to confirm the run model: gh run list --workflow ci.yml --event pull_request --limit 20 +# Post-merge sentinel runs on push (next) + +gh run list --workflow ci-post-merge.yml --event push --branch next --limit 20 + +# Post-merge sentinel runs on push (main) + +gh run list --workflow ci-post-merge.yml --event push --branch main --limit 20 + # Release runs on push (main + next) gh run list --workflow release-check.yml --event push --limit 20 From 1be3e2fbf9132eef37585a789e6600c5ede0a11d Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:02:02 +0200 Subject: [PATCH 7/8] docs(ci): clarify workflow_dispatch branch constraints --- docs/ci-run-model.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ci-run-model.md b/docs/ci-run-model.md index 764dde5a..99702319 100644 --- a/docs/ci-run-model.md +++ b/docs/ci-run-model.md @@ -16,7 +16,9 @@ This document defines how pull request checks and release runs are wired togethe | --- | --- | --- | --- | --- | | `pull_request` | `main`, `next` | ✅ required checks | ❌ | ❌ | | `push` | `main`, `next` | ❌ | ✅ post-merge sentinel | ✅ release run | -| `workflow_dispatch` | selected ref | ❌ | ❌ | ✅ manual release run | +| `workflow_dispatch` | selected ref (only `main`/`next` accepted by job validation) | ❌ | ❌ | ✅ manual release run (main/next only) | + +> **Constraint:** Although GitHub UI lets you choose any ref for `workflow_dispatch`, `release-check.yml` enforces `main` or `next` only and fails early for other refs. ## Required checks for repository ruleset @@ -33,7 +35,7 @@ Release workflow is lean and intentionally does not run a weekly schedule: - Push to `next` → prerelease channel - Push to `main` → stable release channel -- Manual `workflow_dispatch` → ad-hoc release from selected ref +- Manual `workflow_dispatch` → ad-hoc release from `main` or `next` only (validated in the release job) Because release runs happen only after merges, quality gates live in PR required checks, not duplicated release-time full matrices. From 0b358b17331bb2d7a047332d81a9f97bea9aadd0 Mon Sep 17 00:00:00 2001 From: Fabian Jocks <24557998+iamfj@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:14:43 +0200 Subject: [PATCH 8/8] fix(ci): restore release trigger fallback for promotion PR --- .github/workflows/promote-next-to-main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/promote-next-to-main.yml b/.github/workflows/promote-next-to-main.yml index 2223f6e5..80975922 100644 --- a/.github/workflows/promote-next-to-main.yml +++ b/.github/workflows/promote-next-to-main.yml @@ -4,6 +4,9 @@ on: push: branches: - next + release: + types: + - published workflow_dispatch: permissions: @@ -16,6 +19,7 @@ concurrency: jobs: promote: + if: ${{ github.event_name != 'release' || (github.event.release.prerelease == true && github.event.release.target_commitish == 'next') }} runs-on: ubuntu-latest steps: - name: Checkout code