Merge pull request 'fix: keep fork-headed label runs green' (#256) from build/241-fork-labels into main
All checks were successful
CI / test (push) Successful in 3m48s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 21s
CI / action-exercise (push) Successful in 20s
CI / docs-sync-exercise (push) Successful in 20s
release / release (push) Successful in 20s
All checks were successful
CI / test (push) Successful in 3m48s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 21s
CI / action-exercise (push) Successful in 20s
CI / docs-sync-exercise (push) Successful in 20s
release / release (push) Successful in 20s
Reviewed-on: #256 Reviewed-by: glm-bot-andresmgsl <andres+5@heavyduty.builders> Reviewed-by: kimi-bot-andresmgsl <andres+4@heavyduty.builders> Reviewed-by: claude-bot-andresmgsl <andres+1@heavyduty.builders>
This commit is contained in:
commit
6dc8bf6558
8 changed files with 171 additions and 81 deletions
11
.github/workflows/labels-sweep.yml
vendored
11
.github/workflows/labels-sweep.yml
vendored
|
|
@ -3,9 +3,11 @@ 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, so
|
||||
# state, blocker, and handoff reconciliation waits for the caller's scheduled
|
||||
# cadence; the sweep does not apply path-derived scope labels (#241).
|
||||
#
|
||||
# Detached on purpose (#209): every sweep covers every open PR and all
|
||||
# sweeps serialize through ONE shared concurrency group, so GitHub's
|
||||
|
|
@ -24,7 +26,8 @@ name: labels-sweep
|
|||
# taxonomy (its `bootstrap` input defaults to "yes"), exactly what
|
||||
# dispatching the labels caller did before the split. The trigger job's
|
||||
# dispatches carry bootstrap=no — ~20 label upserts per sweep is too chatty
|
||||
# for every board event, the same reason cron runs never bootstrapped.
|
||||
# for every issue and same-repository PR wake, the same reason cron runs never
|
||||
# bootstrapped.
|
||||
#
|
||||
# This cannot loop: reconciler writes use GITHUB_TOKEN, and GitHub does not
|
||||
# create workflow runs from GITHUB_TOKEN-raised events (the trigger's
|
||||
|
|
|
|||
60
.github/workflows/labels.yml
vendored
60
.github/workflows/labels.yml
vendored
|
|
@ -2,12 +2,16 @@ 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 caller uses pull_request_target, not pull_request, so same-repository PRs
|
||||
# keep the base repository's write token without running PR code. On this
|
||||
# Forgejo, unlike GitHub, fork-headed _target runs still receive a read-only
|
||||
# token. Those runs therefore attempt no writes. The scheduled sweep later
|
||||
# reconciles state, blockers, and handoff, but it does not apply path-derived
|
||||
# scope labels; consumers that require those labels on fork heads apply them
|
||||
# manually. The explicit fork_head job below records that disposition as a
|
||||
# successful check. Both write paths execute only for same-repository heads.
|
||||
# Scope reads changed paths and the path mapping through the API and checks out
|
||||
# only the ceremony implementation. Keep it that way (#241).
|
||||
#
|
||||
# The reconcile sweep lived here until #209. Riding the PR-triggered run
|
||||
# meant every displacement in the sweep's shared concurrency queue recorded
|
||||
|
|
@ -19,10 +23,14 @@ name: labels
|
|||
# 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.
|
||||
# trigger job below is its instant wake: it fires on every issue event and
|
||||
# same-repository PR event this caller subscribes to, preserving that part of
|
||||
# the surface that used to run reconcile directly. Same-repository wake latency
|
||||
# (#137) remains seconds-scale, while a displaced sweep cancels on the Actions
|
||||
# tab, attached to no PR. Fork-headed runs cannot dispatch with their read-only
|
||||
# token, so state, blocker, and handoff reconciliation waits for the scheduled
|
||||
# sweep; path-derived scope labels are not applied to fork heads. PR checks show
|
||||
# scope + trigger for same-repository heads, or fork_head for fork heads.
|
||||
#
|
||||
# This cannot loop: the trigger's dispatch and the reconciler's label
|
||||
# writes both use GITHUB_TOKEN. GitHub does not create workflow runs from
|
||||
|
|
@ -60,6 +68,7 @@ jobs:
|
|||
# scope run is clobbered.
|
||||
if: >-
|
||||
github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.action != 'labeled' &&
|
||||
github.event.action != 'unlabeled' &&
|
||||
github.event.action != 'review_requested' &&
|
||||
|
|
@ -106,15 +115,12 @@ jobs:
|
|||
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.
|
||||
# The sweep's instant wake (#209) keeps the whole non-PR event surface and
|
||||
# same-repository PRs. Fork-headed PRs are excluded because this Forgejo
|
||||
# gives their pull_request_target run a read-only token (#241); fork_head
|
||||
# records which reconciliation waits for the sweep and that path-derived
|
||||
# scope labels are not applied there. Non-PR events include workflow_dispatch:
|
||||
# excluding it would make a dispatched labels caller silently do nothing.
|
||||
#
|
||||
# LOUD on failure — never `|| true`: a red trigger is the
|
||||
# misconfiguration alarm. A consumer that bumps the pin without adding
|
||||
|
|
@ -122,6 +128,9 @@ jobs:
|
|||
# `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.
|
||||
if: >-
|
||||
github.event_name != 'pull_request_target' ||
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: dispatch the sweep
|
||||
|
|
@ -199,3 +208,16 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
echo "labels: sweep dispatched — $SWEEP_WORKFLOW on $branch (bootstrap=no)"
|
||||
|
||||
fork_head:
|
||||
# This Forgejo keeps pull_request_target read-only for fork heads (#241),
|
||||
# so name the deliberately unsupported scope write as well as the deferred
|
||||
# state machine instead of letting a green no-op promise full labelling.
|
||||
if: >-
|
||||
github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: explain deferred fork labels
|
||||
run: >-
|
||||
echo "labels: fork head has a read-only token; state, blocker, and handoff reconciliation deferred to the scheduled sweep; path-derived scope labels are not applied to fork heads"
|
||||
|
|
|
|||
16
.github/workflows/self-labels-sweep.yml
vendored
16
.github/workflows/self-labels-sweep.yml
vendored
|
|
@ -7,13 +7,16 @@ 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
|
||||
# while cutting nominal scheduled sweeps from four an hour to one at
|
||||
# claim-reclaim, plus every state, blocker, and handoff transition for a
|
||||
# fork-headed PR on this Forgejo because its pull_request_target token is
|
||||
# read-only (#241). The sweep never applies path-derived scope labels. 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
|
||||
# the classes with no other writer bound the cadence; relax it only as that
|
||||
|
|
@ -22,8 +25,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:
|
||||
|
|
|
|||
26
.github/workflows/self-labels.yml
vendored
26
.github/workflows/self-labels.yml
vendored
|
|
@ -8,8 +8,12 @@ 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 state, blocker, and handoff
|
||||
# reconciliation to the hourly sweep; path-derived scope labels are not
|
||||
# applied to those heads (#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 +30,16 @@ 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, so state, blocker, and handoff
|
||||
# reconciliation waits for the scheduled cadence. The sweep does not apply
|
||||
# path-derived scope labels to those heads (#241).
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ and the reconciler recomputes it from GitHub's own facts.
|
|||
`state:needs-human` means exactly one thing — a human could merge this now —
|
||||
so it requires zero blockers and head-current approvals; anything less and
|
||||
the reconciler takes it back. The author sets it at handoff (the one
|
||||
hand-set state); the `labeled` event fires the sweep that validates the
|
||||
write within seconds.
|
||||
hand-set state). On a same-repository head, the `labeled` event fires the
|
||||
sweep that validates the write within seconds; on a fork head whose
|
||||
`pull_request_target` token is read-only, validation waits for the scheduled
|
||||
sweep cadence (#241).
|
||||
|
||||
## PR blockers — what is in the way? (facts, as many as apply)
|
||||
|
||||
|
|
|
|||
3
changelog.d/241.md
Normal file
3
changelog.d/241.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
### Fixed
|
||||
|
||||
- Fork-headed label runs stay green without attempting forbidden writes, while same-repository heads keep instant scope and reconciliation wakes (#241).
|
||||
|
|
@ -337,14 +337,18 @@ 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 disposition: the scheduled sweep later reconciles state, blockers, and
|
||||
handoff, while path-derived `scope:*` labels are not applied to fork heads.
|
||||
Apply those scope labels manually when an outside contribution needs them.
|
||||
- **`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 +360,10 @@ 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 scheduled state, blocker, and handoff reconciliation. The sweep
|
||||
does not supply their path-derived scope labels.
|
||||
|
||||
The consumer keeps its path mapping in `.github/labeler.yml` and its
|
||||
review panel plus scope taxonomy in `.github/labels.conf`.
|
||||
|
|
@ -384,11 +390,14 @@ 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 state, blocker, and handoff reconciliation
|
||||
# waits for the scheduled sweep; path-derived scope labels require a manual
|
||||
# write when wanted.
|
||||
# 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,22 +434,24 @@ 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
|
||||
# pull_request_review trigger on the labels caller), blocker:ci-red
|
||||
# wake for a review verdict landing (the labels caller has no
|
||||
# pull_request_review trigger), 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
|
||||
# time-based stale / 48h claim-reclaim, plus every state, blocker, and handoff
|
||||
# transition for a fork-headed PR on this Forgejo. The sweep never applies
|
||||
# path-derived scope labels. Issue events and same-repo PR 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.
|
||||
# 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
|
||||
# declared input is part of the contract: a dispatch naming an undeclared
|
||||
# input is refused, and the trigger job goes loudly red.
|
||||
# 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:
|
||||
inputs:
|
||||
bootstrap:
|
||||
|
|
@ -477,17 +488,22 @@ repositories allow check data to be read regardless, but a private consumer
|
|||
needs the explicit reads above; without them the failure appears as an empty
|
||||
`state:*` axis on the board rather than a red workflow run. The labels
|
||||
caller's `actions: write` is different — it is required everywhere, public
|
||||
repos included: the trigger job's dispatch is a write, and without it every
|
||||
event run goes red at the trigger.
|
||||
repos included: the trigger job's dispatch is a write. Without it, issue and
|
||||
same-repository PR event runs go red at the trigger. Fork-headed PR runs do
|
||||
not enter that write path on this Forgejo; they remain green and depend on a
|
||||
healthy scheduled sweep for state, blocker, and handoff reconciliation. That
|
||||
sweep does not apply their path-derived scope labels.
|
||||
|
||||
**The failure mode to know before bumping**: a consumer that bumps its pin
|
||||
to a #209-carrying tag without adding the sweep caller keeps green-looking
|
||||
silence nowhere — the trigger job goes **red on every PR and issue event**
|
||||
(workflow-not-found; likewise on a sweep caller missing its `bootstrap`
|
||||
input, or a labels caller missing `actions: write`), and event-woken sweeps
|
||||
stop until the caller lands. That loudness is deliberate: never read
|
||||
silence, or a green `scope` alone, as health. Make the adoption one atomic
|
||||
PR — pin bump, sweep caller file, `actions: write` line together.
|
||||
to a #209-carrying tag without adding the sweep caller gets a loud red trigger
|
||||
on every issue and same-repository PR event (workflow-not-found; likewise on a
|
||||
sweep caller missing its `bootstrap` input, or a labels caller missing
|
||||
`actions: write`). Fork-headed PR runs deliberately skip that trigger and stay
|
||||
green, so their correctness is proven by the sweep caller's presence and its
|
||||
latest scheduled run, not by the PR check alone. Never read a green
|
||||
`fork_head` disposition as evidence that the scheduled sweep exists. Make the
|
||||
adoption one atomic PR — pin bump, sweep caller file, and `actions: write` line
|
||||
together.
|
||||
|
||||
The `issues:` trigger is available at `0.2.0` and later — `0.2.0` is the
|
||||
first tag carrying ceremony#32. A consumer pinned to `0.1.0` omits it. Adopt
|
||||
|
|
@ -538,20 +554,25 @@ carrying the split:
|
|||
`actions: read` today (crew does); the trigger job's dispatch is a
|
||||
write. The sweep caller keeps `actions: read`.
|
||||
|
||||
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.
|
||||
Bump without the sweep caller and the trigger job goes red on every issue and
|
||||
same-repository PR event. Fork-headed PRs stay green, receive state, blocker,
|
||||
and handoff reconciliation only from the scheduled sweep, and never receive
|
||||
path-derived scope labels automatically; apply those manually when wanted.
|
||||
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
|
||||
`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. The
|
||||
scheduled sweep later reconciles state, blockers, and handoff; it does not
|
||||
apply path-derived scope labels to those heads. 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
|
||||
|
|
|
|||
|
|
@ -150,4 +150,33 @@ done
|
|||
check "pull_request_target keeps the labeled handoff wake" 0 "labeled" \
|
||||
trigger_types "$SELF" pull_request_target
|
||||
|
||||
# ---- fork heads carry a read-only token on this Forgejo (#241) --------------
|
||||
# Same-repo heads keep the existing immediate scope + sweep-dispatch path. A
|
||||
# fork-headed pull_request_target run must attempt no write: both write-capable
|
||||
# jobs exclude it, while one successful job explains exactly what the scheduled
|
||||
# sweep does and does not supply. Require each full normalised expression to
|
||||
# appear intact, so deleting or inverting one of its clauses fails the guard.
|
||||
job_if_expression() { # $1 = file, $2 = job
|
||||
yq -r ".jobs.$2.if // \"\"" "$1" |
|
||||
tr '\n' ' ' |
|
||||
awk '{$1=$1; print}'
|
||||
}
|
||||
check "scope writes only for a same-repo PR head" 0 \
|
||||
"github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.action != 'review_requested' && github.event.action != 'review_request_removed'" \
|
||||
job_if_expression "$REUSABLE" scope
|
||||
check "the sweep trigger preserves non-PR events and excludes fork heads" 0 \
|
||||
"github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository" \
|
||||
job_if_expression "$REUSABLE" trigger
|
||||
check "a fork-headed PR selects the successful explanation job" 0 \
|
||||
"github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository" \
|
||||
job_if_expression "$REUSABLE" fork_head
|
||||
|
||||
fork_head_step() {
|
||||
yq -r '.jobs.fork_head.steps[] | select(.name == "explain deferred fork labels") | .run' \
|
||||
"$REUSABLE" | bash
|
||||
}
|
||||
check "the fork path distinguishes swept state from unsupported scope writes" 0 \
|
||||
"read-only token; state, blocker, and handoff reconciliation deferred to the scheduled sweep; path-derived scope labels are not applied to fork heads" \
|
||||
fork_head_step
|
||||
|
||||
summary
|
||||
|
|
|
|||
Loading…
Reference in a new issue