forked from heavy-duty/ceremony
Round fixes on #200. codex-1 (blocking): the issues narrowing dropped `edited`/`reopened`, but both carry a queue-state change an event uniquely carries — `edited` a body rewrite of the `Blocked by #N` declaration the sweep parses (issueflow-reconcile.sh:179), `reopened` a closed issue re-entering the queue. Dropping them tripped #199's must-fail. Narrow to `[opened, closed, edited, reopened]`, dropping only the churn/validation actions labeled/unlabeled/assigned/unassigned. Trigger tests now pin edited/reopened present and the four dropped; labels.test.sh exact-list updated. kimi (blocking): the "supersedes unreleased #144" prose was false — #144's edited/reopened shipped in 0.3.0. Dissolved: we now keep them. CONSUMERS prose rewritten to the real version history (0.2.0 #32 / 0.3.0 #144 / #199 narrows), and the #137 review-request line corrected from "unreleased" to shipped-in-0.3.0. kimi (non-blocking): reusable labels.yml comment no longer cites */15. codex-2 (AC1 after-measurement / closing) escalated to triage on #199 — held, not guessed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
147 lines
7 KiB
YAML
147 lines
7 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 — and the caller's cron is a backstop, not the wake (#199
|
||
# relaxed it from */15 to hourly): GitHub deprioritises short intervals anyway,
|
||
# so a quiet repo goes a while between ticks. The handoff wakes the sweep
|
||
# itself: the author sets state:needs-human, and the caller's `labeled` event
|
||
# confirms or corrects that optimistic write within seconds. The cron stays as
|
||
# the last resort for a forgotten handoff.
|
||
#
|
||
# 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: "0.3.0"
|
||
|
||
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 }}
|