diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5959c94..9d4860b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,7 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# The checks come from package.json's `check` script — which here ends in +# `pnpm validate`, a repo-specific step the body picks up without being told. name: CI on: @@ -7,44 +9,15 @@ on: branches: - main types: [opened, synchronize, reopened, ready_for_review] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + paths-ignore: + - '**.md' + - 'docs/**' + - LICENSE permissions: contents: read - pull-requests: write jobs: - lint: - name: Lint & Format - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - steps: - - uses: actions/checkout@v7 - - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Lint - run: pnpm lint - - - name: Format check - run: pnpm format - - - name: Typecheck - run: pnpm typecheck - - # The gate the entries actually need: the schema plus the cross-file - # checks it cannot express. Without it a mistyped category or technology - # drops a project off the page, silently. - - name: Validate entries - run: pnpm validate + check: + name: CI + uses: kirchDev/workflows/.github/workflows/_ci-check.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ec89cb9..5af22c1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,43 +7,17 @@ on: branches: [main] pull_request: branches: [main] + types: [opened, synchronize, reopened, ready_for_review] schedule: - cron: '34 23 * * 0' workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - timeout-minutes: 20 + codeql: + name: CodeQL permissions: security-events: write packages: read actions: read contents: read - - strategy: - fail-fast: false - matrix: - language: [actions, javascript-typescript] - - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.9 - with: - languages: ${{ matrix.language }} - build-mode: none - queries: security-extended,security-and-quality - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.9 - with: - category: '/language:${{ matrix.language }}' + uses: kirchDev/workflows/.github/workflows/_codeql.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 diff --git a/.github/workflows/fast-forward-queue.yml b/.github/workflows/fast-forward-queue.yml index db85652..448171b 100644 --- a/.github/workflows/fast-forward-queue.yml +++ b/.github/workflows/fast-forward-queue.yml @@ -1,47 +1,14 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# None of these triggers carries a target branch, so the body resolves it by +# asking whether a `dev` branch exists. Here it does not, and the queue lands +# on `main` — with no value stated in this file. name: Fast-forward Queue -# Lands a queue branch on the integration branch by FAST-FORWARD, so every -# commit arrives individually with its message intact — release-please reads -# those messages to build one CHANGELOG entry per change, and a squash would -# collapse a whole drain into one. See ADR-0008 in kirchDev/infrastructure. -# -# THE HUMAN GATE IS AN APPROVAL ON THE QUEUE PR, and the App is what makes that -# possible. GitHub disables the approve button for a PR's own AUTHOR, so a queue -# PR opened by the sole maintainer could never be approved by them — which is -# why ADR-0008 reached for a `fast-forward-queue` environment instead, and why -# its amendment records that GitHub does not sell required reviewers for a -# private repo on Team. Here the App opens the PR, so the maintainer is not its -# author and can approve it; `pick` then refuses to land anything without that -# approval. See ADR-0010 in kirchDev/infrastructure. -# -# THE APPROVAL IS CHECKED HERE, NOT BY A RULESET. Two reasons, both structural: -# the App bypasses the merge-gate ruleset, so a `required_approving_review_count` -# there would be skipped along with everything else in it; and the ruleset sets -# that count to 0 anyway, which makes GitHub's own `reviewDecision` null rather -# than APPROVED however many approvals a PR carries. The rules the platform -# would have applied — an approver with write access, an approval that is not -# stale — are therefore spelled out in `pick` below. on: - # A queue branch was pushed. The App opens its PR if none is open yet; nothing - # lands on this trigger, because a fresh push has no CI behind it and no - # approval that could still be current. `push` runs the workflow file from the - # pushed branch, which is cut from the integration branch and so carries it. push: branches: - 'ai/queue-**' - # An approval arrived. This is the ONE automatic landing trigger, and it fires - # only from the DEFAULT branch's copy of this file, so it stays inert until the - # rollup lands this on `main`; until then, `workflow_dispatch` is the way. - # - # THE APPROVAL MUST COME LAST — that is a condition, not a style preference. - # An approval is only counted while its `commit_id` is still the head, so any - # push after it un-approves the PR; and nothing fires when CI goes green on an - # already-approved PR. `check_suite: completed` was meant to cover exactly that - # case and cannot: GitHub does not fire it "if the check suite was created by - # GitHub Actions", which is every suite this repo produces. Approve after CI is - # green, or land it with `workflow_dispatch`. See the ADR-0010 amendment in kirchDev/infrastructure. pull_request_review: types: [submitted] workflow_dispatch: @@ -51,325 +18,16 @@ on: required: false type: string -# One fast-forward at a time. Two concurrent runs would race on the same ref and -# the loser's update would stop being a fast-forward mid-flight. -concurrency: - group: fast-forward-queue - cancel-in-progress: false - -env: - # The repo's integration branch — first entry of its branch_flow. - INTEGRATION_BRANCH: main - # Only a head branch with this prefix may be fast-forwarded. A ruleset can - # condition on the TARGET ref only, so this check is the head-branch half of - # the App bypass and lives here on purpose (ADR-0008 in kirchDev/infrastructure). - QUEUE_BRANCH_PREFIX: ai/queue- - jobs: - # The App authors the queue PR. That is the whole point of this job: an - # approval is only possible on a PR the approver did not open, and the drain - # runs under the maintainer's identity. - # - # `github.event.deleted` guards the push that DELETES a queue branch — the ref - # is gone by then and there is nothing to open a PR from. - open: - name: Open the queue PR - if: github.event_name == 'push' && github.event.deleted == false - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - steps: - # Checked with the workflow token, so the App token is minted only when a - # PR actually has to be created — every later push to the same queue - # branch already has one and stops here. - - name: Is a queue PR already open? - id: existing - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ github.ref_name }} - run: | - set -euo pipefail - existing=$(gh pr list --state open --base "$INTEGRATION_BRANCH" --head "$BRANCH" \ - --json number --jq '.[0].number // empty') - if [ -n "$existing" ]; then - echo "PR #$existing is already open for $BRANCH — the push updated it." - echo "create=false" >> "$GITHUB_OUTPUT" - else - echo "No open PR for $BRANCH -> $INTEGRATION_BRANCH." - echo "create=true" >> "$GITHUB_OUTPUT" - fi - - # Same runtime-from-Bitwarden pattern as the landing job below; the PEM is - # never a GitHub secret. - - name: Fetch Queue App PEM from Bitwarden - if: steps.existing.outputs.create == 'true' - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - if: steps.existing.outputs.create == 'true' - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # Opened READY, not draft: ci.yml skips draft PRs, and `pick` skips them - # too, so a draft queue PR would be a PR nothing ever runs on and nothing - # could ever land. The title is cosmetic — a fast-forward never turns it - # into a commit message — but it follows the repo's convention anyway. - - name: Open the queue PR as the App - if: steps.existing.outputs.create == 'true' - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ github.ref_name }} - run: | - set -euo pipefail - - # A quoted heredoc: the body is copied through verbatim, so its - # backticks and asterisks need no shell escaping. - # - # ONE PARAGRAPH PER LINE, however long the line runs. GitHub renders - # comment and PR bodies with hard line breaks on, unlike a .md file in - # the repo, so a hand-wrapped paragraph arrives wrapped at whatever - # column it was written to — a narrow column of text in a wide box, - # re-wrapped again by the reader's own viewport. - body=$(cat <<'EOF' - Queue branch for an AI drain. Every worker PR targets this branch, so CI runs once here instead of once per issue. - - **Approving this PR is what lands it.** Once every check is green and this PR carries an approval on its **current head**, `fast-forward-queue.yml` fast-forwards the integration branch onto it — each commit arriving individually, with its message intact, so release-please still sees one entry per change. Nothing lands without that approval, green CI included. - - A push after the approval invalidates it: the approval is checked against the head commit it was given on, because nothing else dismisses it here. - EOF - ) - - gh pr create \ - --base "$INTEGRATION_BRANCH" \ - --head "$BRANCH" \ - --title "chore(queue): land $BRANCH on $INTEGRATION_BRANCH" \ - --body "$body" - - # Re-checks every candidate against every condition below, so a wrong dispatch - # input, an unapproved PR or a red one ends the run green instead of landing - # something. - pick: - name: Pick the queue PR - if: github.event_name != 'push' - runs-on: ubuntu-latest + fast-forward-queue: + name: Fast-forward Queue permissions: contents: read pull-requests: read - # `gh pr checks` reads the statusCheckRollup, which spans check runs - # (checks) and commit statuses (statuses). A permissions block sets every - # scope it doesn't name to none, so omitting either fails this job. checks: read statuses: read - outputs: - pr: ${{ steps.pick.outputs.pr }} - sha: ${{ steps.pick.outputs.sha }} - steps: - # A dispatch may name exactly one PR; every other trigger (and a dispatch - # with an empty input) considers every open queue PR. Anything that is not - # an open, ready, green, APPROVED queue PR into the integration branch - # leaves `pr` empty and the job ends green. - - name: Pick the queue PR - id: pick - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - INPUT_PR: ${{ inputs.pr }} - run: | - set -euo pipefail - - if [ -n "${INPUT_PR:-}" ]; then - candidates=$INPUT_PR - else - candidates=$(gh pr list --state open --base "$INTEGRATION_BRANCH" \ - --json number,headRefName \ - --jq '.[] | select(.headRefName | startswith(env.QUEUE_BRANCH_PREFIX)) | .number') - fi - - if [ -z "$candidates" ]; then - echo "No open queue PR into $INTEGRATION_BRANCH — nothing to do." - exit 0 - fi - - for n in $candidates; do - pr=$(gh pr view "$n" --json number,state,isDraft,baseRefName,headRefName,headRefOid,isCrossRepository) - state=$(printf '%s' "$pr" | jq -r '.state') - draft=$(printf '%s' "$pr" | jq -r '.isDraft') - base=$(printf '%s' "$pr" | jq -r '.baseRefName') - head=$(printf '%s' "$pr" | jq -r '.headRefName') - sha=$(printf '%s' "$pr" | jq -r '.headRefOid') - fork=$(printf '%s' "$pr" | jq -r '.isCrossRepository') - - # The branch NAME alone is not the head-branch half of the bypass: a - # fork's branch can be called ai/queue-* just as well, and this - # pattern is meant to hold on a public repo too. Only a branch in - # this repository qualifies. - if [ "$fork" != "false" ]; then - echo "PR #$n: head is in a fork — skipping." - continue - fi - - case "$head" in - "$QUEUE_BRANCH_PREFIX"*) ;; - *) - echo "PR #$n: head '$head' is not a queue branch — skipping." - continue - ;; - esac - - # A draft PR is OPEN — `isDraft` is a separate field, and skipping it - # would land a queue branch its author has explicitly marked as not - # ready. Draft is the drain's way of saying "not yet". - if [ "$state" != "OPEN" ] || [ "$draft" != "false" ] || [ "$base" != "$INTEGRATION_BRANCH" ]; then - echo "PR #$n: state=$state draft=$draft base=$base — not a ready PR into $INTEGRATION_BRANCH, skipping." - continue - fi - - # Green means every check, not just the required ones: the queue PR - # is the ONLY gate the drain's commits ever pass through. - if ! gh pr checks "$n"; then - echo "PR #$n: checks are not (yet) all green — skipping." - continue - fi - - # THE HUMAN GATE. `reviewDecision` is deliberately not used: the - # merge-gate ruleset sets required_approving_review_count to 0, and - # GitHub reports reviewDecision as null wherever no review is - # required — however many approvals the PR actually carries. So the - # three things the platform would have checked are checked here: - # the latest review per author, the approval's freshness, and the - # approver's write access. - # - # --paginate emits one array per page, so `.[] | …` streams the - # reviews and `jq -s` collects them back into a single array. - reviews=$(gh api --paginate "repos/$GH_REPO/pulls/$n/reviews" \ - --jq '.[] | {login: .user.login, state: .state, commit: .commit_id}' | jq -s '.') - - # COMMENTED and DISMISSED reviews are dropped BEFORE the per-author - # reduction: a later comment must not clear an approval, and a - # dismissed review must not count as one. group_by is stable, so - # `last` is each author's most recent verdict. - latest=$(printf '%s' "$reviews" \ - | jq -c '[.[] | select(.state == "APPROVED" or .state == "CHANGES_REQUESTED")] - | group_by(.login) | map(last)') - - if [ "$(printf '%s' "$latest" | jq -r '[.[] | select(.state == "CHANGES_REQUESTED")] | length')" != "0" ]; then - echo "PR #$n: changes are requested — skipping." - continue - fi - - # An approval names the commit it was given on. Nothing dismisses a - # stale one here — dismiss_stale_reviews_on_push is false, and the - # App bypasses the ruleset that carries it — so the workflow does it - # itself: approve, then push, and the PR is unapproved again. - approvers=$(printf '%s' "$latest" \ - | jq -r --arg sha "$sha" '.[] | select(.state == "APPROVED" and .commit == $sha) | .login') - - if [ -z "$approvers" ]; then - echo "PR #$n: no approval on the current head $sha — skipping." - continue - fi - - # An approval only gates anything if the approver could have landed - # this themselves. `authorAssociation` on the review is too coarse - # to lean on — COLLABORATOR covers read- and triage-only access. - approved=false - for login in $approvers; do - perm=$(gh api "repos/$GH_REPO/collaborators/$login/permission" --jq '.permission') || perm= - case "$perm" in - admin | maintain | write) - echo "PR #$n: approved on $sha by $login ($perm)." - approved=true - break - ;; - *) - echo "PR #$n: $login approved but has permission '${perm:-unreadable}' — not counted." - ;; - esac - done - - if [ "$approved" != "true" ]; then - echo "PR #$n: no approval from anyone with write access — skipping." - continue - fi - - echo "pr=$n" >> "$GITHUB_OUTPUT" - echo "sha=$sha" >> "$GITHUB_OUTPUT" - echo "PR #$n: green, approved queue PR $head -> $base at $sha." - break - done - - # No `environment:` — see the header. The gate is the approval `pick` verified, - # and it is pinned to a SHA: this job fast-forwards onto the exact commit that - # was approved, so a push landing between the two jobs cannot ride along. - fast-forward: - name: Fast-forward the integration branch - needs: pick - if: needs.pick.outputs.pr != '' - runs-on: ubuntu-latest - # Everything here runs on the App token; the workflow token needs nothing. - permissions: {} - steps: - # The PEM is never a GitHub secret — same runtime-from-Bitwarden pattern as - # release-please.yml. Unlike that one this uses the purpose-built "kirchDev - # Queue" App, which carries `contents: write` plus `pull_requests: write` - # (ADR-0010 in kirchDev/infrastructure, for the `open` job above) and nothing else, and is the - # merge-gate bypass actor on the integration branch - # (the SSOT in kirchDev/infrastructure). - # - # BWS_ACCESS_TOKEN is shared with this repo's other App-token workflows — - # org-level on kirchDev, a repo secret on the TitusKirch account. - - name: Fetch Queue App PEM from Bitwarden - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # The App bypasses the merge gates only, so `required_signatures` from the - # integrity ruleset still binds every commit being fast-forwarded in: one - # unsigned worker commit anywhere in the queue branch fails this step with - # a 422, and the fix belongs on the worker's signing setup, not here. - # - # A ref PATCH without `force` is fast-forward-only: GitHub rejects it with - # 422 if the integration branch is not an ancestor of the queue head, which - # is exactly the guarantee wanted — the queue branch is rebased, never the - # history rewritten. GitHub closes the PR as merged once its head is - # reachable from the base. - - name: Fast-forward the integration branch - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - PR: ${{ needs.pick.outputs.pr }} - SHA: ${{ needs.pick.outputs.sha }} - run: | - set -euo pipefail - gh api -X PATCH "repos/$GH_REPO/git/refs/heads/$INTEGRATION_BRANCH" \ - -f sha="$SHA" -F force=false - { - echo "## Queue branch landed" - echo "" - echo "| Property | Value |" - echo "|----------|-------|" - echo "| PR | #$PR |" - echo "| Branch | \`$INTEGRATION_BRANCH\` |" - echo "| Commit | \`$SHA\` |" - } >> "$GITHUB_STEP_SUMMARY" + uses: kirchDev/workflows/.github/workflows/_fast-forward-queue.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 + with: + pr: ${{ inputs.pr }} + secrets: + BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }} diff --git a/.github/workflows/queue-branch.yml b/.github/workflows/queue-branch.yml index 5d3d14a..fa5bab3 100644 --- a/.github/workflows/queue-branch.yml +++ b/.github/workflows/queue-branch.yml @@ -1,175 +1,22 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# This repo integrates on `main` — no `dev` branch. The body reads the +# integration branch off the pull request's own base, so nothing here says it +# twice. name: Queue Branch -# Cuts the queue branch a worktree drain's pull requests are grouped behind, and -# retargets each worker PR at it. This is the step the drain deliberately cannot -# take: `work.queueBranch` only makes a worker AIM at an open `ai/queue-*`, and -# the loop holds no write to that branch on purpose, so cutting it belongs to -# the repo. Without this workflow the key is inert — every worker PR falls back -# to the integration branch, which is exactly the mode-off path. See ADR-0012 in kirchDev/infrastructure. -# -# Which PRs are grouped is the RUN's to decide, not this repo's: only a drain -# started with `work.queueBranch: true` cuts its branches under `ai/queue/`, and -# only those reach the job below. A repo carrying this file therefore states -# that it CAN group, never that it always will. -# -# `fast-forward-queue.yml` owns the rest: its `open` job turns the pushed branch -# into a PR, and `pick` + `fast-forward` land that PR once it is green and -# approved. Nothing here opens a PR and nothing here lands one. on: pull_request: - # THE BASE IS FILTERED AT THE TRIGGER, not in a job-level `if`: that `if` - # cannot read `env`, so the integration branch would have to be repeated as - # a literal there. Keep this list and INTEGRATION_BRANCH in step. branches: - main - # `opened` only. A retarget is what this workflow DOES, so reacting to - # `edited` would have it answer its own edit; and a push to an already - # grouped PR needs nothing from here. types: [opened] -# Shared with fast-forward-queue.yml, not merely with other runs of this file. -# Cutting a branch and landing one must never interleave: a branch cut from an -# integration tip that a landing is about to move would no longer contain that -# tip, and the fast-forward window closes the moment it doesn't. -concurrency: - group: fast-forward-queue - cancel-in-progress: false - -env: - # The repo's integration branch — first entry of its branch_flow in the SSOT - # (tofu/data/github-{orgs,users}/*.yml in kirchDev/infrastructure). Change it - # here AND in the trigger's - # `branches:` above if that flow changes. - INTEGRATION_BRANCH: main - # The prefix fast-forward-queue.yml will accept as a head branch. Cutting - # anything else would produce a branch that workflow refuses to land. - QUEUE_BRANCH_PREFIX: ai/queue- - jobs: - cut: - name: Cut the queue branch - # THE `ai/queue/` PREFIX IS THE RUN'S OWN SIGNAL that it asked to be grouped. - # A drain under `work.queueBranch: false` cuts `ai/-` and is left - # alone here, so the profile the run was started with decides whether its - # PRs are grouped — not this repo merely carrying the workflow. Matching a - # bare `ai/` would group every worker PR, which makes the two fleet profiles - # indistinguishable in any repo that has this file. - # - # A fork's branch can be called `ai/queue/*` just as well, and a fork PR gets - # no secrets — the Bitwarden step would fail rather than skip. - # - # The queue PR itself needs no exclusion: the App opens it with an - # `ai/queue-` head, which does not match `ai/queue/` (hyphen, not - # slash), so it can never be retargeted onto itself. - if: >- - github.event.pull_request.head.repo.full_name == github.repository - && startsWith(github.event.pull_request.head.ref, 'ai/queue/') - runs-on: ubuntu-latest + queue-branch: + name: Queue Branch permissions: contents: read - # Retargeting the worker PR is the one write the workflow token makes; the - # branch itself is created on the App token below, for the reason given - # there. pull-requests: write - steps: - # Checked with the workflow token, so the App token is minted only when a - # branch actually has to be cut — every worker PR after the first finds - # the queue branch already open and stops before Bitwarden. - - name: Is a queue branch already open? - id: existing - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - run: | - set -euo pipefail - - # The OPEN PR is the marker, not the branch: a queue branch whose PR - # has landed is done, and its branch may well still exist. - open=$(gh pr list --state open --base "$INTEGRATION_BRANCH" \ - --json headRefName \ - --jq '[.[] | select(.headRefName | startswith(env.QUEUE_BRANCH_PREFIX))] | .[0].headRefName // ""') - - if [ -n "$open" ]; then - echo "Queue branch $open is already open — retargeting onto it." - echo "branch=$open" >> "$GITHUB_OUTPUT" - echo "create=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Named for the integration tip it is cut from, which is the one fact - # that decides whether it can still be fast-forwarded later. - tip=$(gh api "repos/$GH_REPO/git/ref/heads/$INTEGRATION_BRANCH" --jq '.object.sha') - echo "No open queue branch — cutting from $INTEGRATION_BRANCH at $tip." - echo "branch=$QUEUE_BRANCH_PREFIX${tip:0:7}" >> "$GITHUB_OUTPUT" - echo "sha=$tip" >> "$GITHUB_OUTPUT" - echo "create=true" >> "$GITHUB_OUTPUT" - - # Same runtime-from-Bitwarden pattern as fast-forward-queue.yml; the PEM is - # never a GitHub secret. BWS_ACCESS_TOKEN is org-level on kirchDev and a repo - # secret on the TitusKirch account. - - name: Fetch Queue App PEM from Bitwarden - if: steps.existing.outputs.create == 'true' - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - if: steps.existing.outputs.create == 'true' - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # THE APP TOKEN IS WHAT MAKES THE NEXT WORKFLOW FIRE, and that is the whole - # reason it is minted here rather than pushing the ref with the workflow - # token. A ref created with GITHUB_TOKEN raises no `push` event, so - # fast-forward-queue.yml's `open` job would never run and the branch would - # sit there with no PR — the exact dead end this workflow exists to close. - # - # Nothing is checked out in this job, so no branch content ever executes - # alongside the token (the same rule the `open` job is written under). - - name: Cut the queue branch - if: steps.existing.outputs.create == 'true' - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ steps.existing.outputs.branch }} - SHA: ${{ steps.existing.outputs.sha }} - run: | - set -euo pipefail - gh api -X POST "repos/$GH_REPO/git/refs" \ - -f ref="refs/heads/$BRANCH" -f sha="$SHA" - echo "Cut $BRANCH at $SHA." - - # Done on the workflow token deliberately: a base change is not a push, so - # nothing downstream needs to be woken by it, and the App token stays out - # of every step that doesn't need it. - # - # This is also where the CI saving lands. where `ci.yml` is - # scoped to the integration branches: once the base is `ai/queue-*` - # further pushes to the worker PR run nothing — CI runs once, on the queue - # PR, instead of once per issue. - - name: Retarget the worker PR - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - PR: ${{ github.event.pull_request.number }} - BRANCH: ${{ steps.existing.outputs.branch }} - run: | - set -euo pipefail - gh pr edit "$PR" --base "$BRANCH" - { - echo "## Worker PR grouped" - echo "" - echo "| Property | Value |" - echo "|----------|-------|" - echo "| PR | #$PR |" - echo "| Base | \`$BRANCH\` |" - echo "| Cut this run | \`${{ steps.existing.outputs.create }}\` |" - } >> "$GITHUB_STEP_SUMMARY" + uses: kirchDev/workflows/.github/workflows/_queue-branch.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 + secrets: + BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}