From e639e67f09dd109c76dc95bc7f703e8e58303383 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 23:59:17 +0000 Subject: [PATCH] docs(labels): split fork and same-repo wake latency --- .github/workflows/labels-sweep.yml | 7 +-- .github/workflows/self-labels-sweep.yml | 13 ++++-- .github/workflows/self-labels.yml | 22 +++++---- changelog.d/241.md | 3 ++ docs/CONSUMERS.md | 61 ++++++++++++++----------- 5 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 changelog.d/241.md diff --git a/.github/workflows/labels-sweep.yml b/.github/workflows/labels-sweep.yml index d021e2b..7e2e3a1 100644 --- a/.github/workflows/labels-sweep.yml +++ b/.github/workflows/labels-sweep.yml @@ -3,9 +3,10 @@ name: labels-sweep # jobs that rode labels.yml until #209. Triggers and permissions live in the # caller; docs/CONSUMERS.md carries the complete caller stub # (workflow_dispatch plus the hourly cron, which relocated here with the -# sweep). Board events still yield a sweep within seconds: labels.yml's -# trigger job dispatches this workflow's caller on every event it used to -# run reconcile on. +# sweep). Issue events and same-repository PR events still yield a sweep within +# seconds: labels.yml's trigger job dispatches this workflow's caller on those +# events. Fork-headed PR events carry a read-only token on this Forgejo and +# wait for the caller's scheduled cadence instead (#241). # # Detached on purpose (#209): every sweep covers every open PR and all # sweeps serialize through ONE shared concurrency group, so GitHub's diff --git a/.github/workflows/self-labels-sweep.yml b/.github/workflows/self-labels-sweep.yml index b929cb5..cb04ebe 100644 --- a/.github/workflows/self-labels-sweep.yml +++ b/.github/workflows/self-labels-sweep.yml @@ -7,12 +7,14 @@ name: labels-sweep on: # The consumer owns this cadence (#203). Hourly is the recommended default # when no other engine drives board state: the cron is then the sweep's ONLY - # wake for four transition classes — a review verdict landing (there is no + # wake for a review verdict landing (there is no # pull_request_review trigger on the labels caller), blocker:ci-red set or # cleared (no check_suite/check_run/workflow_run), a blocker:conflict when # ANOTHER PR merges under this one, and the time-based stale / 48h - # claim-reclaim. The labels caller's events carry the rest in seconds, one - # trigger-job dispatch away. Hourly trades ≤1h of latency on those four + # claim-reclaim, plus every fork-headed PR transition on this Forgejo because + # its pull_request_target token is read-only (#241). Issue events and + # same-repository PR events carry the rest in seconds, one trigger-job + # dispatch away. Hourly trades ≤1h of latency on the scheduled classes # while cutting nominal scheduled sweeps from four an hour to one at # GitHub's 1-minute billing floor. Do not delete the cron: it is their # discovery path. If another engine writes some of those transitions, only @@ -22,8 +24,9 @@ on: # A manual full-board sweep. A bare dispatch (input default "yes") also # bootstraps the taxonomy on a fresh repo — what dispatching the labels # caller did before #209. The reusable's trigger job wakes this workflow - # with bootstrap=no on every board event — an event-woken sweep must not - # re-upsert ~20 labels each time — so declaring this input is part of the + # with bootstrap=no on every issue and same-repository PR event — an + # event-woken sweep must not re-upsert ~20 labels each time — so declaring + # this input is part of the # caller contract: a dispatch naming an undeclared input is refused, and # the trigger job goes loudly red. workflow_dispatch: diff --git a/.github/workflows/self-labels.yml b/.github/workflows/self-labels.yml index 8b6b8c1..cd1c6b2 100644 --- a/.github/workflows/self-labels.yml +++ b/.github/workflows/self-labels.yml @@ -8,8 +8,10 @@ name: labels # Since #209 this caller carries the PR/issue event surface only. The # reconcile sweep no longer rides these runs — the reusable's trigger job # dispatches the sweep caller (self-labels-sweep.yml here), which owns the -# hourly cron and the manual/bootstrap workflow_dispatch. A board event -# below still yields a sweep within seconds, one dispatch hop later. +# hourly cron and the manual/bootstrap workflow_dispatch. Issue events and +# same-repository PR events below still yield a sweep within seconds, one +# dispatch hop later. Fork-headed PRs carry a read-only token on this Forgejo, +# so their successful labels run leaves writes to the hourly sweep (#241). on: # Narrowed (#199) to the actions that carry a queue-state change the hourly # cron cannot wait one cadence for — dropping only labeled/unlabeled/assigned/ @@ -26,14 +28,14 @@ on: issues: types: [opened, closed, edited, reopened] pull_request_target: - # Every PR arrives from a fork, so these carry the head/draft/review facts - # the sweep derives state:* from. labeled/unlabeled are the handoff wake — - # the author's optimistic state:needs-human write, confirmed or corrected - # here in seconds (#11); synchronize re-derives on every push; - # review_requested/review_request_removed wake the sweep that clears (or - # restores) blocker:unrequested — without them the one event that makes - # the label false could not clear it, and a quiet repo wore the red flag - # until the advisory cron (#137). + # These carry the head/draft/review facts the sweep derives state:* from. + # Same-repository heads wake that sweep in seconds; fork heads cannot write + # with this Forgejo's read-only token and wait for its scheduled cadence. + # labeled/unlabeled are the same-repository handoff wake — the author's + # optimistic state:needs-human write, confirmed or corrected here in + # seconds (#11); synchronize re-derives on every push; review_requested/ + # review_request_removed clear (or restore) blocker:unrequested on that + # same instant path (#137). types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] permissions: contents: read diff --git a/changelog.d/241.md b/changelog.d/241.md new file mode 100644 index 0000000..8c52d7e --- /dev/null +++ b/changelog.d/241.md @@ -0,0 +1,3 @@ +### Fixed + +- Fork-headed label runs now stay green without attempting writes their read-only token cannot make, while same-repository heads keep instant labeling (#241). diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index 9afc321..6573fd5 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -337,14 +337,16 @@ The labels automation is two reusable workflows since #209, adopted together at the same pin: - **`labels.yml`** — the event-facing half, called on PR and issue events. - Two jobs: additive path-based `scope:*` labels, and a few-seconds - `trigger` job that wakes the sweep by dispatching the consumer's sweep - caller (a REST `POST` to the forge's own + Same-repository PRs keep two write-capable jobs: additive path-based + `scope:*` labels, and a few-seconds `trigger` job that wakes the sweep by + dispatching the consumer's sweep caller (a REST `POST` to the forge's own `${GITHUB_API_URL}/repos/{owner}/{repo}/actions/workflows/{file}/dispatches`, plain `GITHUB_TOKEN` — `workflow_dispatch` is one of the two documented exemptions from the token's no-retrigger rule, so no PAT anywhere in the path and no loop: the sweep dispatches - nothing). + nothing). On this Forgejo a fork-headed `pull_request_target` token is + read-only, so those two jobs do not run; a successful `fork_head` job names + the deferral, and the scheduled sweep owns their labels. - **`labels-sweep.yml`** — the reconcile sweep: PR state, blockers, handoff, stale status, the issue work queue, and the `needs-ruling` invariants on both surfaces — the bare-flag check and the 7-day @@ -356,8 +358,9 @@ together at the same pin: as fake red CI that GitHub refuses to rerun (crew#250: `gh run rerun` and its `--failed`/`--job` forms all decline a queue-displaced run). Behind its own caller, a displaced sweep cancels on the - Actions tab, attached to no PR; PR checks show `scope` and the green - `trigger` only. + Actions tab, attached to no PR. Same-repository PR checks show `scope` and + the green `trigger`; fork-headed PRs show the green `fork_head` disposition + and wait for the scheduled sweep cadence. The consumer keeps its path mapping in `.github/labeler.yml` and its review panel plus scope taxonomy in `.github/labels.conf`. @@ -384,11 +387,12 @@ The complete event-facing caller is: name: labels on: pull_request_target: - # Fork PRs; these carry the head/draft/review facts state:* derives from. - # labeled/unlabeled are the handoff wake (state:needs-human confirmed here); - # synchronize re-derives on every push. review_requested/review_request_removed - # (shipped in 0.3.0, ceremony#137) wake the sweep that clears - # blocker:unrequested when the panel is asked. + # These carry the head/draft/review facts state:* derives from. Same-repo + # heads take the instant write + sweep-dispatch path; this Forgejo gives + # fork heads a read-only token, so they wait for the scheduled sweep. + # labeled/unlabeled are the same-repo handoff wake; synchronize re-derives + # on every push. review_requested/review_request_removed shipped in 0.3.0 + # (ceremony#137) and wake the same-repo sweep when the panel is asked. types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] # Available at 0.2.0 and later (the first tag carrying ceremony#32); a # consumer pinned to 0.1.0 omits this block. @@ -425,20 +429,21 @@ name: labels-sweep on: # The consumer owns this cadence (#203). Hourly is the recommended default # when no other engine drives board state: the cron is then the sweep's only - # wake for four transition classes — a review verdict landing (no + # wake for a review verdict landing (no # pull_request_review trigger on the labels caller), blocker:ci-red # set/cleared, blocker:conflict when another PR merges under this one, and - # time-based stale / 48h claim-reclaim. The labels caller's events carry the - # rest in seconds, one trigger-job dispatch away. Hourly trades ≤1h of - # latency on those four while cutting nominal scheduled sweeps from four an - # hour to one at GitHub's 1-minute floor. Do not delete the cron: it is their - # discovery path. If another engine writes some of those transitions, only - # the classes with no other writer bound the cadence; relax it only as that - # list shrinks. + # time-based stale / 48h claim-reclaim, plus every fork-headed PR transition + # on this Forgejo. Issue events and same-repo PR + # events carry the rest in seconds, one trigger-job dispatch away. Hourly + # trades ≤1h of latency on the scheduled classes while cutting nominal + # sweeps from four an hour to one at GitHub's 1-minute floor. Do not delete + # the cron: it is their discovery path. If another engine writes some of + # those transitions, only the classes with no other writer bound the cadence; + # relax it only as that list shrinks. schedule: [{cron: "0 * * * *"}] # A manual full-board sweep. A bare dispatch (input default "yes") also # bootstraps the taxonomy on a fresh repo. The labels caller's trigger job - # wakes this workflow with bootstrap=no on every board event, so the + # wakes this workflow with bootstrap=no on every issue and same-repo PR event, so the # declared input is part of the contract: a dispatch naming an undeclared # input is refused, and the trigger job goes loudly red. workflow_dispatch: @@ -542,16 +547,18 @@ Bump without the sweep caller and the trigger job goes red on every PR and issue event — the loud failure mode above — so never split these four edits across PRs. -`pull_request_target` is intentional: fork PRs need the base repository's -token to write labels. The reusable workflows execute no PR code. They check -out only the consumer's base branch and the pinned ceremony implementation. +`pull_request_target` is intentional: same-repository PRs keep the base +repository's write token without executing PR code. This Forgejo still gives +fork-headed `_target` runs a read-only token, so they attempt no writes and +leave labels to the scheduled sweep. The reusable workflows check out only the +consumer's base branch and the pinned ceremony implementation. The #52 ruling invariants ride exactly these triggers — but the caller above is no longer the #18 shape, so adopting current triggers is a stub edit, not a bare pin bump. `review_requested` and `review_request_removed` on -`pull_request_target:` shipped in `0.3.0` (ceremony#137) — the wake that -clears `blocker:unrequested` the moment the panel is asked, without which a -quiet repo wears that flag until the backstop cron; a consumer picks them up -by pinning `0.3.0` or later, never through mixed refs. +`pull_request_target:` shipped in `0.3.0` (ceremony#137). It clears +`blocker:unrequested` the moment the panel is asked on a same-repository head; +fork heads wait for the sweep cadence on this Forgejo. A consumer picks the +events up by pinning `0.3.0` or later, never through mixed refs. `.github/labels.conf` has one mandatory panel setting, one mandatory `triage-actors` setting, zero or more optional per-author panel rows, and