All checks were successful
CI / test (pull_request) Successful in 3m2s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 46s
@codex-reviewer-andresmgsl's second review, both points taken. The refs action goes back to `forge_preflight || exit 1`.97e63achad it exit 0 with a notice so the PR check would not be red, and that conflated two different questions: "this action cannot produce a verdict" is the ACTION's contract and must stay a refusal, while "this check should not block the board" is the CALLER's decision. The caller now carries it — refs-guard.yml skips unless github.server_url is github.com, mirroring forge_detect positively. A skipped check is a green head; an action that reports success it did not earn is not. The leaked preflight_err temp file goes with the revert. The workflow guard asked the wrong question. `command -v gh` alone passes the moment a Forgejo runner image happens to ship gh, and then dispatches against a forge that cannot serve it — the client/forge mismatch forge_preflight exists to prevent. It decides the FORGE first now, mirroring forge_detect positively, and the binary second. The source guard splits to match: a declaration guarded only by binary presence is reported, with a fixture that fails on exactly that shape. The warning text was also wrong on the facts, as noted: issue-event sweeps ARE this caller's event-driven wakes, so they are precisely what is lost. It now says the hourly scheduled sweep survives and every event-driven wake through this caller does not, until #205. Point 1 of that review — jq 1.6 accepting an empty payload — was already fixed in728102a, pushed before the review landed. Verified under the runner's jq 1.6 as well as 1.7: 28 test files, 0 failed both ways. shellcheck 0.10.0 (CI's pin), actionlint, self-ref, marker, vendored, changelog-armed all clean with every file tracked. Refs #198
158 lines
8.6 KiB
YAML
158 lines
8.6 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.
|
||
# Keep it that way.
|
||
#
|
||
# The reconcile sweep lived here until #209. Riding the PR-triggered run
|
||
# meant every displacement in the sweep's shared concurrency queue recorded
|
||
# a CANCELLED `reconcile` check on some PR — read as red CI by every human
|
||
# and agent, though the surviving sweep does the displaced run's work. Two
|
||
# field facts made that untenable (crew#250): a displaced run cannot be
|
||
# rerun — `gh run rerun`, `--failed`, and `--job` all refuse — so a victim
|
||
# PR has no manual escape hatch; and the displacing burst is deterministic,
|
||
# one `review_requested` event per panelist per request, so every review
|
||
# round displaces runs and the rate scales with panel size. The
|
||
# sweep now lives in labels-sweep.yml behind its own caller, and the
|
||
# trigger job below is its wake: it fires on every event this caller
|
||
# subscribes — the exact surface that used to run reconcile directly — so
|
||
# the wake latency (#137) is unchanged, while a displaced sweep cancels on
|
||
# the Actions tab, attached to no PR. PR checks show scope + trigger only.
|
||
#
|
||
# This cannot loop: the trigger's dispatch and the reconciler's label
|
||
# writes both use GITHUB_TOKEN. GitHub does not create workflow runs from
|
||
# GITHUB_TOKEN-raised events — workflow_dispatch and repository_dispatch
|
||
# are the two documented exemptions, which is exactly why the trigger can
|
||
# wake the sweep with no PAT anywhere in the path — and the sweep itself
|
||
# dispatches nothing. Agent writes use a PAT and therefore do trigger —
|
||
# exactly the asymmetry wanted.
|
||
on:
|
||
workflow_call:
|
||
inputs:
|
||
sweep_workflow:
|
||
description: >-
|
||
Filename of the consumer's sweep caller — the workflow that
|
||
calls labels-sweep.yml (docs/CONSUMERS.md carries the stub).
|
||
The trigger job dispatches it by this name. Override it only
|
||
when the caller file is not named labels-sweep.yml (ceremony's
|
||
own dogfood names it self-labels-sweep.yml).
|
||
type: string
|
||
required: false
|
||
default: labels-sweep.yml
|
||
|
||
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.6.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 the sweep (#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 }}
|
||
|
||
trigger:
|
||
# The sweep's wake (#209). No `if:`: reconcile carried none, so the
|
||
# trigger keeps the whole event surface the caller subscribes —
|
||
# workflow_dispatch of the labels caller itself included. That cannot
|
||
# double-fire bootstrap: this dispatch always carries bootstrap=no, so
|
||
# a dispatched labels caller yields one plain sweep, and the taxonomy
|
||
# bootstrap fires solely on a manual dispatch of the sweep caller
|
||
# (whose input defaults to "yes"). Excluding workflow_dispatch here
|
||
# would instead make a dispatched labels caller do nothing at all —
|
||
# a silent no-op run is worse than a redundant sweep.
|
||
#
|
||
# LOUD on failure — never `|| true`: a red trigger is the
|
||
# misconfiguration alarm. A consumer that bumps the pin without adding
|
||
# the sweep caller (workflow-not-found), without its declared
|
||
# `bootstrap` input (unexpected input), or without `actions: write`
|
||
# on this caller (permission denied) fails HERE, visibly on the PR,
|
||
# instead of silently never sweeping again.
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: dispatch the sweep
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
SWEEP_WORKFLOW: ${{ inputs.sweep_workflow }}
|
||
# This step speaks gh and says so, the same declaration
|
||
# actions/refs-not-closing carries (#198 spec 4). A workflow has no
|
||
# shell to call forge_preflight from, so the refusal is inline
|
||
# below; #205 owns the REST port that removes both.
|
||
CEREMONY_FORGE_CLIENT: gh
|
||
run: |
|
||
# Two questions, not one. @codex-reviewer-andresmgsl: a guard that
|
||
# only asks `command -v gh` passes the moment a Forgejo runner image
|
||
# happens to ship gh — and then runs a GitHub dispatch against a
|
||
# forge that cannot serve it, which is the client/forge mismatch
|
||
# forge_preflight exists to prevent. So the FORGE is decided first,
|
||
# mirroring forge_detect positively (only github.com is accepted;
|
||
# anything else, known or not, is refused — "Never 'probably
|
||
# github'"), and the binary is checked second.
|
||
#
|
||
# A warning, not a failure: this trigger is the misconfiguration
|
||
# alarm for a CONSUMER's missing sweep caller, and reddening every
|
||
# sweep on a forge for a gap #205 already owns would drown that
|
||
# signal. #205 ports the dispatch to REST and removes all of this.
|
||
if [ "${GITHUB_SERVER_URL:-}" != "https://github.com" ]; then
|
||
echo "::warning::labels: the sweep was NOT woken from this trigger — it dispatches with \`gh\` against GitHub, and this is not a GitHub forge (GITHUB_SERVER_URL=${GITHUB_SERVER_URL:-unset}). #205 ports it to REST. The hourly SCHEDULED sweep still runs; every event-driven wake through this caller — issue events included — is unavailable until then."
|
||
exit 0
|
||
fi
|
||
if ! command -v gh >/dev/null 2>&1; then
|
||
echo "::warning::labels: the sweep was NOT woken from this trigger — this runner does not carry \`gh\`. #205 ports the dispatch to REST. The hourly SCHEDULED sweep still runs; every event-driven wake through this caller is unavailable until then."
|
||
exit 0
|
||
fi
|
||
gh workflow run "$SWEEP_WORKFLOW" -R "$GITHUB_REPOSITORY" -f bootstrap=no
|