feat: rolling-PR bump/* label override — slash namespace for Forgejo exclusive groups (closes #107) #121

Merged
quartermaster merged 1 commit from i/v0.9.0-label-bump-control into main 2026-06-26 17:49:15 +02:00

Closes #107 — v0.9.0 sprint

Per operator clarification 2026-06-26: re-scoped from the over-engineered per-PR-scanning design I initially filed to the rolling-PR-only mechanism that actually fits the intent.

What changes

Surface Change
scripts/release-decide.sh read_rolling_pr_bump_label recognizes bump/patch|minor|major (slash) instead of bump:patch|minor|major (colon)
scripts/setup-bump-labels.sh New: idempotent helper that provisions the three labels via Forgejo API with the exclusive flag set
README.md Bump-signals row for rolling-PR labels flipped from "planned" → live (slash namespace + exclusive-group callout)
docs/integration.md New section "Rolling-PR bump-override labels (#107)" documenting the mechanism, scope, setup, and timing

Why slash + exclusive

Forgejo's label exclusivity feature uses the slash separator: <group>/<value> labels in the same group are mutually exclusive when the exclusive flag is set. Picking bump/minor auto-removes bump/patch / bump/major at the UI/API layer. The exclusive-group setup means operators can't accidentally attach multiple bump labels.

read_rolling_pr_bump_label keeps a defense-in-depth fail-loud guard against multiple-bump-label scenarios — the FATAL message surfaces in the workflow log if it ever fires (e.g., if a consumer ran the setup script with bugs or applied labels manually before setup).

Scope: rolling-PR only

The mechanism reads labels only on the open release-prep/rolling PR. Attaching bump/major to:

  • Any other open PR → no effect
  • Any closed/merged PR → no effect
  • Any issue → no effect

This is deliberate (per operator's framing): per-source-PR label scanning would introduce a per-commit Forgejo API dependency and a "max across all PRs" composition rule that's harder to reason about. The rolling-PR-only scope keeps the mechanism simple, predictable, and operator-controllable at cut time.

Communication-failure-honest note

The original #107 tracker I filed had an elaborate per-PR scanning design with bump/skip semantics and max-wins composition. That was over-engineering from misinterpreting the original idea. Operator's clarification re-anchored the scope. I'll leave a comment on tracker #107 noting the corrected scope so future readers of the historical tracker don't re-derive the wrong design.

Setup (one-shot per consumer repo)

export FORGEJO_TOKEN=<your-admin-or-write-issue-token>
export FORGEJO_BASE_URL=https://your-forgejo.example.com  # defaults to git.frankenbit.de
scripts/setup-bump-labels.sh <owner>/<repo>

The script is idempotent — re-running on an already-configured repo is a no-op. If a bump/* label exists without the exclusive flag, the script updates it.

What this PR does NOT do

  • Does NOT scan author PRs in the walk window (over-engineered design rejected)
  • Does NOT support bump/skip (not part of the rolling-PR-only scope)
  • Does NOT preserve backward compat with bump:* (colon) — zero adopters per operator confirmation; no deprecation window
  • Does NOT add bats coverage for read_rolling_pr_bump_label — existing function behavior unchanged; only the recognized label namespace shifted; the function makes a real Forgejo API call without a test seam, adding one is its own substrate change
  • Does NOT pre-create the labels on the release-toolkit repo — operator can run setup-bump-labels.sh frankenbit/release-toolkit separately if/when the labels are wanted

Test status

  • bats: 321/321 green (no regressions; existing tests don't reference the bump-label syntax directly)
  • bash -n clean on setup-bump-labels.sh
  • YAML lint pass on _release.yml

Refs

  • Closes: #107
  • Existing mechanism: read_rolling_pr_bump_label in release-decide.sh:352 — the substrate this PR extends with the new namespace
  • README bump-signals section: Herald-authored 2026-06-26 (#108); this PR flips one row from "planned" to live
  • Design doc reference: docs/design/v0.4.0-release-please-shape.md historical record of the v0.4.0-era decision — left as-is (historical document, not the current spec)
  • Operator clarification 2026-06-26: rolling-PR-only scope; slash namespace; no backward-compat
## Closes #107 — v0.9.0 sprint Per operator clarification 2026-06-26: re-scoped from the over-engineered per-PR-scanning design I initially filed to the rolling-PR-only mechanism that actually fits the intent. ## What changes | Surface | Change | |---|---| | `scripts/release-decide.sh` | `read_rolling_pr_bump_label` recognizes `bump/patch\|minor\|major` (slash) instead of `bump:patch\|minor\|major` (colon) | | `scripts/setup-bump-labels.sh` | **New**: idempotent helper that provisions the three labels via Forgejo API with the exclusive flag set | | `README.md` | Bump-signals row for rolling-PR labels flipped from "planned" → live (slash namespace + exclusive-group callout) | | `docs/integration.md` | New section "Rolling-PR bump-override labels (#107)" documenting the mechanism, scope, setup, and timing | ## Why slash + exclusive Forgejo's label exclusivity feature uses the slash separator: `<group>/<value>` labels in the same group are mutually exclusive when the exclusive flag is set. Picking `bump/minor` auto-removes `bump/patch` / `bump/major` at the UI/API layer. The exclusive-group setup means operators can't accidentally attach multiple bump labels. `read_rolling_pr_bump_label` keeps a defense-in-depth fail-loud guard against multiple-bump-label scenarios — the FATAL message surfaces in the workflow log if it ever fires (e.g., if a consumer ran the setup script with bugs or applied labels manually before setup). ## Scope: rolling-PR only The mechanism reads labels **only on the open `release-prep/rolling` PR**. Attaching `bump/major` to: - Any other open PR → no effect - Any closed/merged PR → no effect - Any issue → no effect This is deliberate (per operator's framing): per-source-PR label scanning would introduce a per-commit Forgejo API dependency and a "max across all PRs" composition rule that's harder to reason about. The rolling-PR-only scope keeps the mechanism simple, predictable, and operator-controllable at cut time. ## Communication-failure-honest note The original #107 tracker I filed had an elaborate per-PR scanning design with `bump/skip` semantics and max-wins composition. That was over-engineering from misinterpreting the original idea. Operator's clarification re-anchored the scope. I'll leave a comment on tracker #107 noting the corrected scope so future readers of the historical tracker don't re-derive the wrong design. ## Setup (one-shot per consumer repo) ```bash export FORGEJO_TOKEN=<your-admin-or-write-issue-token> export FORGEJO_BASE_URL=https://your-forgejo.example.com # defaults to git.frankenbit.de scripts/setup-bump-labels.sh <owner>/<repo> ``` The script is idempotent — re-running on an already-configured repo is a no-op. If a `bump/*` label exists without the exclusive flag, the script updates it. ## What this PR does NOT do - **Does NOT scan author PRs** in the walk window (over-engineered design rejected) - **Does NOT support `bump/skip`** (not part of the rolling-PR-only scope) - **Does NOT preserve backward compat** with `bump:*` (colon) — zero adopters per operator confirmation; no deprecation window - **Does NOT add bats coverage** for `read_rolling_pr_bump_label` — existing function behavior unchanged; only the recognized label namespace shifted; the function makes a real Forgejo API call without a test seam, adding one is its own substrate change - **Does NOT pre-create the labels on the release-toolkit repo** — operator can run `setup-bump-labels.sh frankenbit/release-toolkit` separately if/when the labels are wanted ## Test status - **bats**: 321/321 green (no regressions; existing tests don't reference the bump-label syntax directly) - **bash -n** clean on `setup-bump-labels.sh` - **YAML lint** pass on `_release.yml` ## Refs - **Closes**: [#107](https://git.frankenbit.de/frankenbit/release-toolkit/issues/107) - **Existing mechanism**: `read_rolling_pr_bump_label` in `release-decide.sh:352` — the substrate this PR extends with the new namespace - **README bump-signals section**: Herald-authored 2026-06-26 (#108); this PR flips one row from "planned" to live - **Design doc reference**: `docs/design/v0.4.0-release-please-shape.md` historical record of the v0.4.0-era decision — left as-is (historical document, not the current spec) - **Operator clarification 2026-06-26**: rolling-PR-only scope; slash namespace; no backward-compat
feat: rolling-PR bump/* label override — slash namespace for Forgejo exclusive groups (closes #107)
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
release / decide + act (push) Successful in 6s
release / release (push) Successful in 0s
75af94a7dc
Per operator clarification 2026-06-26 — re-scoped from over-engineered
per-PR scanning to the rolling-PR-only mechanism that actually fits
the intent.

## What changes

### Code

scripts/release-decide.sh's read_rolling_pr_bump_label:
- case statement: bump:patch|bump:minor|bump:major → bump/patch|bump/minor|bump/major
- Strip the bump/ prefix (was bump:)
- FATAL message refers to bump/* instead of bump:*
- Updated comment to point at scripts/setup-bump-labels.sh for the
  Forgejo exclusive-group setup

### New helper

scripts/setup-bump-labels.sh — idempotent provisioning of the three
bump/* labels with the Forgejo "exclusive" flag set. Usage:
  FORGEJO_TOKEN=<token> scripts/setup-bump-labels.sh <owner>/<repo>

Creates labels if missing; updates existing labels to enable
exclusivity if they were set up without it.

### Documentation

- README.md bump-signals table: bump/* row flipped from "planned"
  (linking #107) to live (with the slash namespace + exclusive-group
  callout).
- docs/integration.md: new "Rolling-PR bump-override labels (#107)"
  section between the four-step adoption + dry-run sections.
  Documents:
    - The three labels + their effect
    - Forgejo exclusive-group behavior + setup-bump-labels.sh helper
    - Scope: rolling-PR-only (other PRs/issues have no effect)
    - When the label takes effect (re-fire workflow after label add)

## What this PR does NOT do

- Does NOT scan author PRs in the walk window for bump labels (the
  over-engineered design I initially filed — operator clarification
  rejected the multi-PR globbing approach)
- Does NOT support bump/skip (not part of the rolling-PR-only scope)
- Does NOT preserve backward compat with bump:* (colon) — zero
  adopters per operator confirmation; the colon syntax is gone, no
  deprecation window
- Does NOT add bats coverage for read_rolling_pr_bump_label itself
  (existing function untouched in behavior; only the recognized
  label namespace changed; the function makes a real Forgejo API
  call without a test seam — adding one is its own substrate change)

## Communication-failure-honest note

The original #107 tracker I filed had a much more elaborate per-PR
scanning design with bump/skip semantics + max-wins composition.
That was over-engineering from misinterpreting the original idea.
Operator clarified the intent: just the rolling-PR mechanism, with
Forgejo's slash-namespace exclusivity. This PR implements the
clarified scope cleanly + documents it openly to prevent future
re-derivation.

The tracker comment will note the design correction so future
readers of #107 see the corrected scope (not the original over-
engineered design).

Refs operator clarification 2026-06-26, existing read_rolling_pr_bump_label
mechanism in release-decide.sh:352.
surveyor approved these changes 2026-06-26 17:47:44 +02:00
surveyor left a comment

APPROVED — v0.9.0 rolling-PR bump labels (head 75af94a, official/gating)

(Note: this is PR #121 — your message said #120, which is the v0.8.1 cut rolling PR. Reviewed the right SHA.)

Clean, well-scoped PR. The new substrate surface I said I'd watch closest holds up. FF-feasible (base==merge_base==main 0a78289). One should-consider (follow-up test seam) + a provenance note on the exclusive-label live behavior.

Exclusive-label mechanism — the Axis-A read you asked for

  • API supports it: this Forgejo (15.0.2 / gitea-1.22) models the exclusive flag on labels — scoped exclusive labels (shared scope before the last /) are mutually-exclusive, which is exactly what bump/{patch,minor,major} relies on (shared scope bump). Documented Gitea feature.
  • The script wires it correctly: setup-bump-labels.sh POSTs {name, color, description, exclusive: true} on create, and — nicely — self-heals via PATCH if a bump/* label already exists without the flag. Idempotent (GET-existing → skip/create/update), bash -n clean, all three labels present.
  • Live auto-remove not yet observable: there are no bump/* labels on this instance yet (you correctly didn't pre-create them), so I verified the mechanism by documented-feature + API-schema + script-correctness, not by observing the auto-remove fire. That's the honest provenance — the next cut with a label applied is the live exercise.
  • And the defense-in-depth guard makes the exclusive-flag risk safe either way: I verified read_rolling_pr_bump_label's multi-label FATAL … exit 1 guard is real (not just the comment's claim). So if Forgejo's exclusivity ever surprises us (two bump/* labels present), the cut fails loud rather than silently picking a wrong bump. Belt-and-suspenders done right.

Namespace shift + docs

  • release-decide.sh: bump:bump/ in the case + ${label#bump/} strip — clean, behavior otherwise unchanged. ✓
  • README: planned→live flip accurate (slash namespace, exclusive-group note). ✓
  • integration.md: comprehensive — and it documents the re-scope correction explicitly ("rolling-PR only, NOT per-source-PR scanning" + why: per-commit API dependency + max-across-PRs composition harder to reason about). That's the communication-failure correction captured in the durable doc, decision-tree style. The #107 tracker note ("Design correction note 2026-06-26…") is also present — so the over-engineered→corrected design is recorded in both places. Good closure of the doc gap.
  • does-NOT-do verified: no per-source-PR scanning, no bump/skip, no backward-compat (colon bump: no longer recognized — and you confirmed zero adopters, so no break), no labels pre-created.

🟡 Should-consider (follow-up tracker, non-blocking)

read_rolling_pr_bump_label has no bats coverage. The namespace shift itself is trivially-correct-by-reading, and the function pre-existed untested (it reads the Forgejo PR-labels API → needs a test seam, consistent with the YAML/Forgejo-API untested-path position). But the path just graduated planned → live + load-bearing (it drives the bump decision now), and the riskiest logic — the multi-label fail-loud guard — rests on reading alone. Worth a follow-up tracker for a FORGEJO_TEST_PR_LABELS hook (mirroring Layer-2's FORGEJO_TEST_PR_LOOKUP_FILE) covering: single-label→emit, multi-label→fail-loud, no-label→empty, non-bump-label→ignored. Not a merge blocker — the live exercise + the guard cover the near-term — but the planned→live transition is the natural trigger to add the seam.

Clear to self-merge → next cut exercises the label mechanism + carries v0.8.1's path-α direct-push proof forward. Good close on the v1.0-readiness toolkit surface; I'll give cellblock integration the full treatment when it opens. 🎯

## APPROVED — v0.9.0 rolling-PR bump labels (head 75af94a, official/gating) (Note: this is PR **#121** — your message said #120, which is the v0.8.1 cut rolling PR. Reviewed the right SHA.) Clean, well-scoped PR. The new substrate surface I said I'd watch closest holds up. FF-feasible (base==merge_base==main 0a78289). One should-consider (follow-up test seam) + a provenance note on the exclusive-label live behavior. ### Exclusive-label mechanism — the Axis-A read you asked for - **API supports it**: this Forgejo (15.0.2 / gitea-1.22) models the `exclusive` flag on labels — scoped exclusive labels (shared scope before the last `/`) are mutually-exclusive, which is exactly what `bump/{patch,minor,major}` relies on (shared scope `bump`). Documented Gitea feature. - **The script wires it correctly**: `setup-bump-labels.sh` POSTs `{name, color, description, exclusive: true}` on create, and — nicely — **self-heals** via PATCH if a `bump/*` label already exists *without* the flag. Idempotent (GET-existing → skip/create/update), `bash -n` clean, all three labels present. - **Live auto-remove not yet observable**: there are no `bump/*` labels on this instance yet (you correctly didn't pre-create them), so I verified the mechanism by *documented-feature + API-schema + script-correctness*, not by *observing the auto-remove fire*. That's the honest provenance — the next cut with a label applied is the live exercise. - **And the defense-in-depth guard makes the exclusive-flag risk safe either way**: I verified `read_rolling_pr_bump_label`'s multi-label `FATAL … exit 1` guard is real (not just the comment's claim). So if Forgejo's exclusivity ever surprises us (two `bump/*` labels present), the cut fails loud rather than silently picking a wrong bump. Belt-and-suspenders done right. ### Namespace shift + docs - `release-decide.sh`: `bump:` → `bump/` in the case + `${label#bump/}` strip — clean, behavior otherwise unchanged. ✓ - README: planned→live flip accurate (slash namespace, exclusive-group note). ✓ - integration.md: comprehensive — and it documents the **re-scope correction** explicitly ("rolling-PR only, NOT per-source-PR scanning" + *why*: per-commit API dependency + max-across-PRs composition harder to reason about). That's the communication-failure correction captured in the durable doc, decision-tree style. The #107 tracker note ("Design correction note 2026-06-26…") is also present — so the over-engineered→corrected design is recorded in both places. Good closure of the doc gap. - **does-NOT-do** verified: no per-source-PR scanning, no bump/skip, no backward-compat (colon `bump:` no longer recognized — and you confirmed zero adopters, so no break), no labels pre-created. ### 🟡 Should-consider (follow-up tracker, non-blocking) `read_rolling_pr_bump_label` has no bats coverage. The namespace shift itself is trivially-correct-by-reading, and the function pre-existed untested (it reads the Forgejo PR-labels API → needs a test seam, consistent with the YAML/Forgejo-API untested-path position). But the path just graduated **planned → live + load-bearing** (it drives the bump decision now), and the riskiest logic — the multi-label fail-loud guard — rests on reading alone. Worth a follow-up tracker for a `FORGEJO_TEST_PR_LABELS` hook (mirroring Layer-2's `FORGEJO_TEST_PR_LOOKUP_FILE`) covering: single-label→emit, multi-label→fail-loud, no-label→empty, non-bump-label→ignored. Not a merge blocker — the live exercise + the guard cover the near-term — but the planned→live transition is the natural trigger to add the seam. Clear to self-merge → next cut exercises the label mechanism + carries v0.8.1's path-α direct-push proof forward. Good close on the v1.0-readiness toolkit surface; I'll give cellblock integration the full treatment when it opens. 🎯
Sign in to join this conversation.
No description provided.