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.1.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 with: repository: heavy-duty/ceremony ref: ${{ env.CEREMONY_SELF_REF }} path: .ceremony-src - name: reconcile state + stale uses: ./.ceremony-src/actions/labels-reconcile with: bootstrap: ${{ github.event_name == 'workflow_dispatch' && 'yes' || 'no' }} env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }}