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 — labeler reads changed paths via the # API, 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 */15 cron is advisory: GitHub deprioritises # short intervals hard enough that a quiet repo goes hours 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.2.0" jobs: scope: # Not on labeled/unlabeled: those events change no paths, so labeler has # nothing new to derive — and label churn is precisely what they are. if: >- github.event_name == 'pull_request_target' && github.event.action != 'labeled' && github.event.action != 'unlabeled' runs-on: ubuntu-latest concurrency: group: labels-scope-${{ github.event.pull_request.number }} cancel-in-progress: true steps: - uses: actions/labeler@v5 with: # labeler reads the consumer's .github/labeler.yml via the API # additive only — a hand-applied scope must survive the machine sync-labels: false 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: labeler # reads 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 }}