forked from heavy-duty/ceremony
The pure consumer path cannot resolve CEREMONY_SELF_REF: "0.4.1" before the candidate creates that tag, and release.yml's self-checkout is hardcoded to heavy-duty/ceremony — so the pin is rewritten to the candidate SHA, which resolves there today. Drill scaffolding only; this ref is never merged.
149 lines
7.1 KiB
YAML
149 lines
7.1 KiB
YAML
name: labels
|
||
# Reusable half of the labels automation. Triggers and permissions live in
|
||
# the caller; docs/CONSUMERS.md carries the complete caller stub.
|
||
#
|
||
# The caller uses pull_request_target, not pull_request: every PR in this
|
||
# family arrives from a fork, where pull_request runs with a READ-ONLY token
|
||
# and cannot label anything. _target is safe in this workflow because no PR
|
||
# code is ever checked out or executed — scope reads changed paths and the
|
||
# path mapping via the API and checks out only the ceremony implementation,
|
||
# and reconcile checks out the BASE branch only. Keep it that way.
|
||
#
|
||
# There is no pull_request_review_target, so a review landing cannot wake this
|
||
# workflow directly — which is why the caller's cron is load-bearing, not a
|
||
# safety net (#199 relaxed it from */15 to hourly, but did NOT drop it). The
|
||
# cron is the sweep's only discovery path for every transition no subscribed
|
||
# event carries: a verdict landing, blocker:ci-red set/cleared, a
|
||
# blocker:conflict when another PR merges under this one, and the time-based
|
||
# stale / 48h claim-reclaim. Where an event IS subscribed the wake is direct —
|
||
# the handoff sets state:needs-human and the caller's `labeled` event confirms
|
||
# or corrects that optimistic write within seconds.
|
||
#
|
||
# This cannot loop: reconciler writes use GITHUB_TOKEN, and GitHub does not
|
||
# create workflow runs from GITHUB_TOKEN-triggered events. Agent writes use a
|
||
# PAT and therefore do trigger — exactly the asymmetry wanted.
|
||
on:
|
||
workflow_call:
|
||
|
||
env:
|
||
# A called workflow arrives without its repository. Keep this literal pin
|
||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||
CEREMONY_SELF_REF: "9a229ee643291c27dbd404fa97e4dcc7e6ed3b17"
|
||
|
||
jobs:
|
||
scope:
|
||
# Not on labeled/unlabeled: those events change no paths, so scope has
|
||
# nothing new to derive — and label churn is precisely what they are.
|
||
# review_requested/review_request_removed likewise change no paths — they
|
||
# exist to wake reconcile (#137) — and running labeler on them widens
|
||
# exactly the window #130 documents, where a label written during a
|
||
# scope run is clobbered.
|
||
if: >-
|
||
github.event_name == 'pull_request_target' &&
|
||
github.event.action != 'labeled' &&
|
||
github.event.action != 'unlabeled' &&
|
||
github.event.action != 'review_requested' &&
|
||
github.event.action != 'review_request_removed'
|
||
runs-on: ubuntu-latest
|
||
concurrency:
|
||
group: labels-scope-${{ github.event.pull_request.number }}
|
||
cancel-in-progress: true
|
||
steps:
|
||
# actions/labeler@v5 held this seat until #130. Even with
|
||
# sync-labels: false it wrote the WHOLE label set — PUT of
|
||
# (labels-fetched-at-job-start ∪ derived) — so a label applied while
|
||
# the job ran was silently removed: ceremony#128 lost its `release`,
|
||
# the merge door's declared-intent read, two seconds after the
|
||
# builder set it. v6/v7 write the same way, so the step was replaced
|
||
# rather than repinned. labels-scope reads the consumer's
|
||
# .github/labeler.yml and the changed paths via the API, and its
|
||
# only write is an additive POST of the derived scopes: a label
|
||
# applied mid-job survives by construction.
|
||
#
|
||
# Still no PR code: both checkouts below fetch the ceremony
|
||
# implementation only. The dogfood checkout rides github.sha — the
|
||
# base-branch commit the workflow file itself came from, so the
|
||
# script and workflow can never skew — and doubles as the #11
|
||
# bootstrap: ceremony's own labels must work before any release tag
|
||
# exists for the pinned checkout to fetch.
|
||
- uses: actions/checkout@v4
|
||
if: github.repository == 'heavy-duty/ceremony'
|
||
with:
|
||
repository: ${{ github.repository }}
|
||
ref: ${{ github.sha }}
|
||
- uses: actions/checkout@v4
|
||
if: github.repository != 'heavy-duty/ceremony'
|
||
with:
|
||
repository: heavy-duty/ceremony
|
||
ref: ${{ env.CEREMONY_SELF_REF }}
|
||
- uses: ./actions/labels-scope
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||
# the BASE branch commit — a PR must not label itself by editing
|
||
# the mapping it is judged by
|
||
CONFIG_REF: ${{ github.sha }}
|
||
|
||
reconcile:
|
||
runs-on: ubuntu-latest
|
||
# ONE shared group: every reconcile sweeps every open PR, so cron and
|
||
# PR-event runs must serialize or two sweeps race the same PR's labels
|
||
# and both pass the request-the-human-once guard.
|
||
concurrency:
|
||
group: labels-reconcile
|
||
cancel-in-progress: false
|
||
steps:
|
||
# pull_request_target is required for fork PR write permission. It is
|
||
# safe here because no PR code is ever checked out or executed:
|
||
# labels-scope reads the mapping and changed paths via the API, and
|
||
# reconcile checks out the BASE branch only. Keep it that way.
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
repository: ${{ github.repository }}
|
||
ref: ${{ github.event.repository.default_branch }}
|
||
- uses: actions/checkout@v4
|
||
# The self-consumption bypass — release.yml's twin, and load-bearing
|
||
# for the same reason (#11): ceremony's own labels bootstrap must
|
||
# run BEFORE any release tag exists for this checkout to fetch — the
|
||
# release label the merge door reads is created by that dispatch, so
|
||
# without the bypass the first release deadlocks on its own pin. The
|
||
# base-branch checkout above already IS ceremony on the dogfood
|
||
# path.
|
||
if: github.repository != 'heavy-duty/ceremony'
|
||
with:
|
||
repository: heavy-duty/ceremony
|
||
ref: ${{ env.CEREMONY_SELF_REF }}
|
||
path: .ceremony-src
|
||
# Two steps, mutually exclusive `if:`s, because a `uses:` path must be
|
||
# a literal — the same fork release.yml's CEREMONY_DIR env line
|
||
# papers over for `run:` steps, which composite `uses:` has no
|
||
# equivalent of.
|
||
- name: reconcile state + stale
|
||
if: github.repository != 'heavy-duty/ceremony'
|
||
uses: ./.ceremony-src/actions/labels-reconcile
|
||
with:
|
||
bootstrap: ${{ github.event_name == 'workflow_dispatch' && 'yes' || 'no' }}
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
- name: reconcile state + stale (dogfood — the workspace IS ceremony)
|
||
if: github.repository == 'heavy-duty/ceremony'
|
||
uses: ./actions/labels-reconcile
|
||
with:
|
||
bootstrap: ${{ github.event_name == 'workflow_dispatch' && 'yes' || 'no' }}
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
- name: reconcile issue flow
|
||
if: github.repository != 'heavy-duty/ceremony'
|
||
uses: ./.ceremony-src/actions/issueflow-reconcile
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|
||
- name: reconcile issue flow (dogfood — the workspace IS ceremony)
|
||
if: github.repository == 'heavy-duty/ceremony'
|
||
uses: ./actions/issueflow-reconcile
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
REPO: ${{ github.repository }}
|