Labels machinery: reusable workflow + core/scope table split #10
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#10
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #1. Blocked by #2. Independent of the release chain (#3–#9) — can proceed in parallel. Consumed by #13–#15; #16 takes only the label bootstrap.
Goal
Centralize the labels machinery — the family's largest verbatim copy:
labels.yml(78 lines ×3, byte-identical) andlabels-reconcile.sh(486 lines ×3, identical except six embeddedscope:*rows — verified by diff at the pinned SHAs: 14 diff lines, all in the label table around L307–L312).Note the boundary: the release ceremony needs only the
releaselabel to exist. This machinery (thestate:*reconciler, stale sweep, scope labeler) is centralized because it is family-copied, not because #9 depends on it. If sequencing gets tight, everything else ships without this.Deliverables
1.
actions/labels-reconcile/(composite, pattern from #5)labels-reconcile.shverbatim-modulo its two embedded config blocks — this script encodes the whole PR state machine (decide_state, the handoff/optimistic-write validation, the request-the-human-once guard, the stale sweep) and it is identical across repos today; do not refactor it in the same PR that moves it. Inputs: none beyondGH_TOKEN/REPOenv (as today).BOTS=(claude-bot-andresmgsl codex-bot-andresmgsl grok-bot-andresmgsl)at box L33 is per-repo roster, not machinery. Move it to.github/labels.conf(apanel=<login> <login> …line; format decided with the conf parsing, tested the same way; missing line → refuse loudly, a reconciler with an empty panel would mark everything approved).BOTS. Replace the "three formal approvals" constants/phrasing with "every required verdict" so panel size is config, not code.bootstrap_labelsheredoc, box L293–L313):state:building,state:bots-reviewing,state:addressing,state:needs-human,blocker:conflict,blocker:ci-red,blocker:unrequested,merge-next,stale,blocked,release(copy colors/descriptions exactly from the source), plus the issue-flow rows from PR #17's LABELS.md:needs-triage#FBCA04,ready#0E8A16,claimed#1D76DB,epic#5319E7(LABELS.md is the source of truth for their descriptions). Bootstrap-only for now — the reconciler does not manage issue-flow labels until #18.scope:*rows move to a per-repo file:.github/labels.conf, samename|color|descriptionpipe format as the heredoc. Bootstrap = core rows + conf rows; a missing conf is fine (zero scope labels), an unparseable line fails loudly.bootstrap: "yes"|"no"the workflow wires togithub.event_name == 'workflow_dispatch'.2.
.github/workflows/labels.yml(workflow_call)Two jobs, ported from the source workflow with its comments (the
pull_request_targetsafety essay is load-bearing — no PR code is ever checked out or executed; keep it that way and keep the sentence):scope:actions/labeler@v5,sync-labels: false(additive only — a hand-applied scope must survive the machine), skipped onlabeled/unlabeledevents, per-PR concurrency group. The labeler reads the consumer's.github/labeler.ymlvia the API — that file stays per-repo (path globs are inherently repo-specific).reconcile: checkout the consumer's base branch only (never PR code), checkout.ceremony-srcat the self-ref pin (same mechanism as #9 — reuse the same env name and the same guard), run the reconcile action. Single shared concurrency grouplabels-reconcile,cancel-in-progress: false— two sweeps racing one PR's labels both pass the request-once guard (the source comment explains; keep it).Caller stub (consumer's entire
labels.yml— triggers must live here):3. Tests
The reconciler is 486 lines of API-coupled bash; full simulation is out of scope. Required floor:
bash -n+ shellcheck in CI (as everything).decide_stateis a pure function of fetched globals in the source — extract-and-test it if it can be done without rewriting it (source the script with a test guard, the boxtest/cli.shtrick); if not, file a follow-up issue rather than force it here.Acceptance criteria
.github/workflows/labels-bootstrap.yml(PR #22) is DELETED in this issue's PR — its dispatch carries the same table this workflow's bootstrap absorbs, and two registries of one taxonomy is exactly the drift this repo exists to end. Grep the tree forlabels-bootstrapafterwards; zero hits.labels.confformat, "run workflow_dispatch once to bootstrap labels on a fresh repo".Interim note: PR #22 adds a dispatch-only
labels-bootstrap.ymlcarrying the LABELS.md table (the agents team is triage-only, so definitions converge via the Actions token — the same reason this issue's bootstrap works). Delete that file in this issue's PR — its table is the same one the reusable workflow's bootstrap carries.Blocker #2 (scaffold) closed via PR #25 — flipping
blocked→ready. Per the epic's ordering this is independent of the release chain (#3–#9) and can proceed in parallel. Note the interim labels-bootstrap dispatch (PR #22) is already merged; this issue replaces it with the reusable workflow + core/scope table split.Claiming this as codex-bot-andresmgsl. I’m starting the labels machinery port now and will open a draft PR shortly.