build(release-automation): R4 release-plz active mode (workspace-style tags + CHANGELOG)#148
Merged
githubrobbi merged 2 commits intomainfrom May 8, 2026
Merged
Conversation
…e tags + CHANGELOG)
Phase R4 of `docs/architecture/release-automation-plan.md`. Flips
`.github/workflows/release-plz.yml` from R3 SHADOW mode (`release-plz
update`, local-only) to ACTIVE mode (`release-plz/action@v0.5.128`
with `command: release-pr` + `command: release` in two parallel jobs,
mirroring release-plz's own recommended workflow shape).
Settled-pre-execution decisions (recorded in plan §R4 + baseline §11):
* D1. Workspace-style tags — single `v{{ version }}` per release
(`git_tag_name = "v{{ version }}"`) instead of release-plz's default
`{{ package }}-v{{ version }}` per-crate scheme. Honors UFFS as one
product (12 publishable crates moving in lockstep, sharing
`[workspace.package].version`) and keeps the existing `release.yml`
`on: push: tags: [v*]` trigger working with zero migration. Same
pattern as cargo and rustls workspaces; diverges from tokio.
* D2. Workspace-style CHANGELOG — 12 per-package `[[package]]` blocks
with `changelog_path = "CHANGELOG.md"` flatten the per-crate
changelog into the workspace-root file. `changelog_path` cannot be
set at workspace level (release-plz docs explicitly forbid it).
* D3. `git_only = true` workspace baseline — UFFS is unpublished
through R8, so the crates.io registry has no version data to diff
against. release-plz uses git tags as the baseline instead. Forward-
compat note for R8: `git_only = true` and `publish = true` are
mutually exclusive, so R8 will flip this back when ≥1 crate goes live.
* D4. `release_commits` regex filter — restricts release-PR triggers
to `feat:` / `fix:` / `perf:` / `security:` commits, matching the
set that `cliff.toml`'s `commit_parsers` maps to changelog sections.
Without this, every `chore:` / `docs:` / `ci:` push would re-open
the release PR with no-op churn. Single source of truth for the
suppression list.
* D5. Two-job workflow — `release-plz/action` does NOT have a single
"do both" command. Per the release-plz repo's own workflow, R4
ships two parallel jobs: `release-plz-pr` (opens/updates PR) and
`release-plz-release` (creates tag on release-PR merge, no-ops
otherwise).
* D6. Default `GITHUB_TOKEN`, NOT GitHub App / PAT — known limitation:
tags created by release-plz via `GITHUB_TOKEN` do NOT trigger
`release.yml` (per GitHub's anti-loop policy). Three workarounds
documented in the workflow header (GitHub App = canonical; PAT =
simpler; `release.yml workflow_run` trigger = third option). R4
ships with default token to minimize new infra; an "R4.5" follow-up
PR sets up Option A.
* D7. First-release v0.5.91 bootstrap — out of scope for this PR.
The v0.5.90 worktree predates R3.5's dep-version fix (cccf4f1),
so release-plz's `git_only` baseline check fails when comparing HEAD
against v0.5.90 — surfaces as "no version bump proposed" in the
first few R4 runs. Maintainer manually bumps `[workspace.package]
.version` 0.5.90 → 0.5.91 + hand-writes the CHANGELOG entry + pushes
the tag (user push triggers `release.yml` normally). After
bootstrap, release-plz takes over.
Publishing dormancy (UNCHANGED from R3 → R6, see plan §6):
* `release-plz.toml` workspace `publish = false` (first layer).
* No `CARGO_REGISTRY_TOKEN` env var passed to the action (second
layer). Both stay through R7 (OIDC scaffolding) and only flip in
R8 (publishing dress rehearsal for `uffs-time`).
Files modified:
* `release-plz.toml` (~220 LOC added) — `git_only`, `git_tag_name`,
`git_release_name`, `release_commits`, 12 `[[package]]` blocks
with `changelog_path = "CHANGELOG.md"`. Header comment block
rewritten for R4 phase. R6 `release = false` blocks for internal
tools preserved.
* `.github/workflows/release-plz.yml` (rewritten) — two-job pattern
(`release-plz-pr` + `release-plz-release`) using
`release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11`
(v0.5.128, SHA-pinned per supply-chain hygiene). Permissions
elevated `contents: read → write`, `pull-requests: read → write`
at job level. Manual diff-capture and "verify no git mutation"
guard (R3 shadow-only) removed.
* `docs/architecture/release-automation-plan.md` (§R4 rewritten,
dashboard updated) — captures D1-D7 above; R3.5 + R6 dashboard
rows promoted 🟡 → 🟢 (PR #145 cccf4f1); R4 row 🟡 in-progress
pointing at this PR; two new §8.1 deviations log entries (R4
baseline self-healing transient + R4 downstream-trigger
GITHUB_TOKEN limitation).
* `docs/architecture/release-automation-baseline.md` (§11 R4
addendum appended) — durable record of D1-D6 with rationale,
ecosystem precedent, reversibility cost. Includes the maintainer
bootstrap procedure for v0.5.91 as standalone steps.
Validation:
* `cargo check --workspace --all-targets`: pass.
* `cargo fmt --check --all`: pass.
* `taplo format --check release-plz.toml`: pass.
* `actionlint .github/workflows/release-plz.yml`: pass.
* `typos`, `reuse lint`: pass.
* `just lint-pre-push`: 20/20 gates green (97s).
Rollback: `git revert` flips the workflow back to R3 shadow mode.
Any tags created by future release-plz active runs stay (idempotent).
The `release-plz.toml` R4 additions are additive — revert independently.
Refs:
* Phase R4 plan: `docs/architecture/release-automation-plan.md`
* Settled decisions: `docs/architecture/release-automation-baseline.md` §11
* release-plz reference workflow:
https://github.com/release-plz/release-plz/blob/main/.github/workflows/release-plz.yml
githubrobbi
added a commit
that referenced
this pull request
May 8, 2026
… tag creation through release-plz-* PRs only (#151) Why --- The first R4 active-mode workflow run on PR #149's merge (run 25549828912) failed the `release-plz-release` job with: failed to create ref refs/tags/v0.5.91 with sha 113f188... Reference update failed (HTTP 422) Root cause: release-plz's default `release_always = true` makes the `release` job attempt a tag-creation on EVERY push to `main`, racing `auto-tag-release.yml` -> `release.yml` (the existing R3-era path still active until R5 retires the bespoke flow). On PR #149's merge: 1. release-plz fired and tried to recompute the CHANGELOG (the bespoke `update_all_versions.rs` rewrites `## [0.5.90]` -> `## [0.5.91]` in place rather than producing a cliff-style entry, leaving state inconsistent with what release-plz expects). 2. release-plz committed the recomputation locally (synthetic SHA `113f188...`) and tried to tag that SHA. 3. Meanwhile `release.yml` had already created the `v0.5.91` tag at the actual merge commit `5ff321b04`. 4. Two tag-creators racing for the same ref -> release-plz lost -> workflow turned RED. Fix --- Set `release_always = false` workspace-level in `release-plz.toml`. This makes `release-plz release` only fire when the latest commit on `main` is the merge of a PR whose branch starts with `pr_branch_prefix` (`release-plz-`). Coexistence semantics: * Bespoke `just ship` cycles use `release/vX.Y.Z` branch names -> NOT `release-plz-*` -> `release` job NO-OPS cleanly. `auto-tag-release.yml` + `release.yml` remain the sole tag-creator during the R4 -> R5 transition window. * release-plz-driven cycles use `release-plz-vX.Y.Z` branches -> matches the gate -> `release` job fires correctly. Steady-state (post-R5): bespoke flow deleted -> only the `release-plz-*` path remains -> `release_always = false` continues gating correctly without modification. This is the recommended setting for any project using PR-gated releases per release-plz docs: https://release-plz.ieni.dev/docs/config (search for `release_always`). Documentation ------------- Updates two rows in `docs/architecture/release-automation-plan.md` \u00a78.1 deviations log: * Corrects the existing "R4 baseline" row's misprediction ("silently treats as no-baseline" -> actual: HARD-FAIL with `cargo package failed`). Records the v0.5.91 bootstrap details (PR #149 squash-merged to `5ff321b04`; tag created by `auto-tag-release.yml` -> `release.yml`, NOT release-plz). * Adds a new "R4 release-job race" row capturing the symptom + root cause + this fix + the post-R5 forward-compat note. Verification ------------ * `taplo format --check release-plz.toml` -> ok. * `cargo fmt --check --all` -> ok. * `typos release-plz.toml docs/architecture/release-automation-plan.md` -> ok. * Manual cross-check vs release-plz docs: `release_always` field documented in https://release-plz.ieni.dev/docs/config under the `[workspace]` reference; `pr_branch_prefix` already set to `release-plz-` in our config (line 113). Related ------- * PR #148 (R4 active mode landed) * PR #149 (v0.5.91 bootstrap via bespoke flow) * PR #145 (R3.5 dep-version + R6 publishability) * docs/architecture/release-automation-plan.md \u00a78.1 (deviations log)
githubrobbi
added a commit
that referenced
this pull request
May 8, 2026
…153) Phase R5 of `docs/architecture/release-automation-plan.md`. Deletes the parallel `auto-tag-release.yml` + `update_all_versions.rs` + `version-bump` recipes track that has been driving releases since v0.4.x; release-plz (R4 active since 2026-05-08) is now the sole version-bump + tag creator. This is the point-of-no-return milestone for the release-automation initiative; the bespoke flow can no longer be the fallback on the next push to `main`. Removed (~1430 LOC): - `.github/workflows/auto-tag-release.yml` (168 LOC). - `build/update_all_versions.rs` (1073 LOC). - `scripts/ci/ci-pipeline.rs` (53 LOC) — Phase 7 deprecation shim; `REMOVE-AFTER: v0.5.73` marker satisfied at v0.5.92. - `increment_version` + `version_bump` fns in `scripts/ci-pipeline/src/version.rs`. - `STEP_VERSION_INCREMENT` from `ALL_STEPS` in `scripts/ci-pipeline/src/workflow.rs`. - Version-bump step from `run_enhanced_phase2` (ship.rs) and `phase2_optimized` (phases.rs). - `version-bump` recipe in `just/build.just`. - Version-bump step from `quick-deploy` recipe in `just/dev.just`. - `!build/update_all_versions.rs` carve-out in `.gitignore`. Added (~140 LOC, mostly comments + workflow YAML): - `detect-release-bump` short-circuit job in `.github/workflows/release-cache-warm.yml` — diffs `[workspace.package].version` between `HEAD` and `HEAD~1` and skips the warm matrix when the push is a version bump (saves ~165 runner-min/release because `release.yml` rebuilds + caches that same dep graph anyway). - Bridge step in `release-plz.yml`'s release job — `gh workflow run release.yml ...` after release-plz creates the workspace tag. Replaces the GITHUB_TOKEN anti-loop workaround that R4 deferred to a future GitHub App / PAT setup; uses `workflow_dispatch` (explicitly carved out of the anti-loop policy) instead. Flips `git_release_enable = false` in `release-plz.toml` so `release.yml` owns the GitHub Release page (avoids the body-overwrite race that softprops/action-gh-release would otherwise hit when run against a release-plz-created Release with `body_path: release-notes.md`). Doc updates: - `docs/architecture/release-automation-plan.md` — flip R5 row to 🟢, append four deviation log entries (v0.5.91 immutable-release lockout, R5-before-R4-bakein pragmatic acceleration, R5 cache-warm short-circuit, R5 downstream-trigger bridge resolves prior R4 deferred row). - `docs/architecture/dev-flow-implementation-plan.md` — tick the final Phase 7 bake-in checkbox (deprecation shim retired, `REMOVE-AFTER: v0.5.73` satisfied at v0.5.92). - `CONTRIBUTING.md` — rewrite the Release row in the four-layer quality-gates matrix to describe the post-R5 release-plz flow. - `docs/publishing.md` — flip R3.5 / R5 / R6 status rows to landed; R4 stays 🟡 (active, bake-in pending first release-plz-driven release). - `docs/architecture/security/supply-chain-posture.md` — replace `auto-tag-release.yml` reference with the post-R5 chain. - Trailing comments in `release-plz.toml`, `Cargo.toml`, `scripts/ci-pipeline/Cargo.toml`, `.gitignore`, and `scripts/ci-pipeline/src/{version,workflow,ship,phases}.rs` rewritten to describe the post-R5 steady state and explicitly note the R5 retirement of any pre-R5 tooling they referenced. Validation: - `cargo check --workspace --locked --all-targets` green. - `cargo clippy -p uffs-ci-pipeline --locked --all-targets -- -D warnings` green. - `cargo fmt --all` green. - `actionlint` on the two modified workflow YAML files green. - `just gates-drift` — manifest + consumers agree (23 gates). R4 bake-in completes naturally on the next `feat:` / `fix:` / `perf:` / `security:` commit to `main`, which release-plz will turn into the first end-to-end release-plz-driven release (v0.5.93). At that point the R4 row in the dashboard flips to 🟢 in a follow-up commit. Refs: #148 (R4 active-mode flip), #145 (R3.5 internal-dep version requirements + R6 metadata), #151 (`release_always = false` gate), #152 (v0.5.92 manual bootstrap after v0.5.91 immutable-release lockout).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase R4 of
docs/architecture/release-automation-plan.md— flips.github/workflows/release-plz.ymlfrom R3 SHADOW mode (release-plz update, local-only) to ACTIVE mode (release-propens/updates the release PR;releasecreates the tag + GitHub Release on PR merge).This is the workflow-flip-only R4 PR. The first-release v0.5.91 bootstrap (Cargo.toml version bump + hand-written CHANGELOG entry + tag push) is performed manually by the maintainer in a separate operation — see plan §R4 decision 7 + baseline §11 "Maintainer bootstrap procedure".
Settled-pre-execution decisions
Recorded in
release-automation-plan.md§R4 +release-automation-baseline.md§11 for durability:v{{ version }}, not per-crate)release.ymlon: push: tags: [v*]trigger; matchescargo+rustls[[package]]changelog_pathoverrides)changelog_pathgit_only = truebaselinerelease_commitsregex (feat/fix/perf/securityonly)cliff.tomlcommit_parsers; suppresseschore/docs/ci/build/refactor/test/style/revertchurnrelease-plz-pr+release-plz-release)release-plz/actionhas no "do both" command; mirrors release-plz's own workflowGITHUB_TOKEN, NOT GitHub App / PAT (DOCUMENTED LIMITATION)GITHUB_TOKENdon't trigger downstreamrelease.yml(anti-loop policy); 3 workarounds documented; "R4.5" follow-up sets up Option Amainto bootstrapFiles modified
release-plz.tomlgit_only,git_tag_name,git_release_name,release_commits); + 12[[package]]blocks withchangelog_path = "CHANGELOG.md"; header rewritten for R4 phase.github/workflows/release-plz.ymlrelease-plz/action@1528104d(v0.5.128 SHA-pinned).contents: read → write,pull-requests: read → writeat job level. R3 manual diff-capture removed.docs/architecture/release-automation-plan.mddocs/architecture/release-automation-baseline.mdNet diff: 4 files, +662 / -275 = +387 LOC (mostly comments + docs).
Publishing dormancy (UNCHANGED from R3 → R6)
Belt-and-suspenders dormancy through Phase R8. See plan §6.
release-plz.tomlworkspacepublish = false(first layer).CARGO_REGISTRY_TOKENenv var passed to the action (second layer).Both layers stay through R7 (OIDC scaffolding) and only flip in R8 (publishing dress rehearsal for
uffs-time).Validation
cargo check --workspace --all-targetscargo fmt --check --alltaplo format --check release-plz.tomlactionlint .github/workflows/release-plz.ymltypos/reuse lintjust lint-pre-push— 20/20 gates green (52s post-commit)What this PR deliberately does NOT do
auto-tag-release.yml. R5 scope (after ≥2 R4-flow releases bake in).publish = false+ missingCARGO_REGISTRY_TOKENunchanged.release.yml. Trigger contract preserved.Rollback
git revertflips the workflow back to R3 shadow mode. Therelease-plz.tomlR4 additions are additive — revert independently if needed. Any tags created by future release-plz active runs are idempotent (tag-exists guard inrelease.yml).Bake-in plan
After merge, observe one push to
main:release-plz-prruns and either opens a release PR (if any qualifying commits since v0.5.90) or no-ops.release-plz-releaseruns and silently no-ops (HEAD is not yet a release-PR merge).Then maintainer performs the v0.5.91 bootstrap per baseline §11 procedure.
References
docs/architecture/release-automation-plan.md§R4docs/architecture/release-automation-baseline.md§11