Pipeline (3 operator asks): - bump-release.sh: canonical orchestrator (forks + sources + external) - upgrade-forks.sh --to=<tag>: rebase forks with diverged-mode guard - bump-graphics-recipes.sh: map-driven group-aware graphics bumps - check-external-versions.sh: drift checker for Qt6/KF6/Plasma/Mesa/Wayland - refresh-fork-upstream-map.sh: append-only map updater with --check - post-checkout-version-sync.sh + install-git-hooks.sh: opt-in branch hook - external_version_lib.py: shared version-parsing/bumping library - external-upstream-map.toml: ~80 external package entries - bump-fork.sh: deprecated (REDBEAR_I_KNOW_BUMP_FORK_IS_DEPRECATED=1) - RELEASE-BUMP-WORKFLOW.md: operator runbook Quality fixes (8 defects from two independent audits): - blake2b stable cache keys (was hash(), non-portable) - atomic cache writes via os.replace - version_sort_key pre-release demotion (was sorting after finals) - apply_ver_transform re.error tolerance - grep || true (pipefail abort) - cd failure detection in upgrade-forks - sed URL escape (injection hardening) - refresh-fork-upstream-map last-row drop fix Doc cleanup: - Archive 5 obsolete plans to local/docs/archived/ - Remove 14 stale/superseded docs - Update 18 docs to reference bump-release.sh and fix inbound links - TOOLS.md drift fixes
15 KiB
Red Bear OS — Release-Bump Workflow
Status: canonical operator runbook (2026-07-18) Audience: operators performing a Red Bear OS release bump Scope: moving version labels, fork sources, and the external desktop stack forward across a release-branch switch.
This document is the runbook for the release-bump pipeline. It is the companion to two contracts:
local/AGENTS.md§ "Version conventions — two categories" and § "No-fake-version-label rule" — the policies this pipeline enforces.local/scripts/TOOLS.md— the tool inventory (modes, flags, where each tool runs).
If the docs disagree, AGENTS.md is the policy authority and TOOLS.md is the behavioural contract of each script. This document is the workflow that sits on top of both.
TL;DR — the 30-second version
A Red Bear OS release bump is four steps. The first is human; the rest are mechanical or operator-judgement.
| Step | What | Who/What | Touches source? |
|---|---|---|---|
| 1. Cut the branch | Create the release branch on gitea (0.3.2) |
Operator only | No |
| 2. Switch + label-sync | git checkout 0.3.2 → hook rewrites all version labels to match the branch (or run ./local/scripts/bump-release.sh by hand) |
post-checkout hook (opt-in) or operator | No — labels only |
| 3. Source upgrades | ./local/scripts/bump-release.sh --with-sources --with-external rebases eligible forks to newer upstream tags and bumps the Qt6/KF6/Plasma recipes |
Operator | Yes — real rebases + recipe bumps |
| 4. Rebuild + stabilize | Rebuild, fix patch fallout, regenerate lockfiles | Operator | Yes |
Nothing in steps 2–4 ever auto-commits. Every step that changes the tree
prints the exact git add / git commit sequence the operator should run.
The four-step release model in detail
Step 1 — Cut the release branch (operator only)
Per local/AGENTS.md § "BRANCH AND SUBMODULE POLICY", release branches are
created by the operator only — one per Red Bear OS release cycle. Agents
must never create branches. The branch name MUST be an anchored semver:
0.3.1, 0.3.2, 0.4.0. Non-semver names (master, submodule/*,
recovered/*) are never release branches and are ignored by every tool in
this pipeline.
The branch is created on the canonical gitea repo
(https://gitea.redbearos.org/vasilito/RedBear-OS.git) from the prior
release tip, then fetched locally:
git fetch origin
git checkout 0.3.2 # tracks origin/0.3.2
Nothing else happens at branch creation time. The bump machinery fires on the checkout, not on the branch creation.
Step 2 — Switch and label-sync
When the operator checks out the new release branch, two things can happen:
A. If the post-checkout hook is installed (opt-in; see
local/docs/HOOKS.md):
git checkout 0.3.2
# post-checkout-version-sync.sh fires automatically:
# - guards pass (semver branch, clean tree, no rebase in progress, …)
# - detects root Cargo.toml is at 0.3.1, not 0.3.2
# - runs: sync-versions.sh --no-regen (labels only)
# - prints the follow-up hint pointing at step 3
The hook does the label-only half of the bump:
- Cat 0 (cookbook root
Cargo.toml):version = "0.3.1"→"0.3.2". - Cat 1 (in-house crates under
local/recipes/*/source/): same. - Cat 2 (upstream forks under
local/sources/*/): the suffix moves —0.9.0+rb0.3.1→0.9.0+rb0.3.2. The upstream base (0.9.0) is preserved because no source rebase has happened yet.
The hook explicitly does NOT:
- regenerate lockfiles (
--regenis never passed — it is a separate explicit step in step 4), - touch fork source content,
- commit anything,
- run on non-semver branches or dirty trees,
- block the checkout under any circumstance (always exits 0).
B. If the hook is not installed, run the orchestrator by hand:
./local/scripts/bump-release.sh # label sync + reports, no mutations beyond labels
# or equivalently for labels only:
./local/scripts/sync-versions.sh --no-regen
bump-release.sh (no args) delegates the label rewrite to
sync-versions.sh --no-regen and additionally prints a fork-upstream
report and an external-version report so the operator can see what step 3
will need to touch.
After step 2 the tree has correct labels but possibly stale sources and lockfiles. The operator should commit the label bump:
git add -A
git commit -m "release: sync version labels to 0.3.2"
Step 3 — Source upgrades
This is the expensive half. It is always operator-initiated via explicit flags and never triggered by the hook.
3a. Cat 2 fork source bumps — --with-sources
./local/scripts/bump-release.sh --with-sources
For each fork in local/fork-upstream-map.toml, bump-release.sh:
- Classifies the fork by its map
modecolumn (decision tree below). - For eligible forks (mode
snapshotortracked),git ls-remote --tagsthe upstream URL, find the latest anchored-semver tag, and compare to the fork's current base (the<X.Y.Z>before+rb). - If upstream moved: invoke
upgrade-forks.sh --to=<tag> <fork>, which fetches + pinsupstream_reftorefs/tags/<tag>, creates a backup branch (rb-backup/<fork>), resets, re-applies the Red Bear net-diff, falls back to cherry-pick if needed, regenerates the fork lockfile, and runsverify-fork-functions.sh. - On success: set the fork
Cargo.tomlversion = "<tag>+rb<branch>", update map column 3 in place, and print the exact commit commands for the parent repo and thesubmodule/<fork>branch. - On failure: report, leave map/label untouched. No partial state.
Per the no-fake-label rule (local/AGENTS.md § "No-fake-version-label
rule (STRICT)"), labels never move without matching source content. When
upstream is unchanged, only the +rb<branch> suffix moves (already done in
step 2). When upstream moved, the fork is really rebased first, then the
label moves to match.
3b. External desktop stack — --with-external
./local/scripts/bump-release.sh --with-external
# or just the version report first:
./local/scripts/check-external-versions.sh
This drives the rewritten, map-driven bump-graphics-recipes.sh over the
Qt6/KF6/Plasma desktop stack and singletons (mesa, libdrm, libwayland,
sddm, …) declared in local/external-upstream-map.toml. Groups resolve
their version once (e.g. all KF6 frameworks move to the same
frameworks/X.Y/), symlinked recipes are deduped by realpath so qtbase is
bumped exactly once, and each recipe is validated with
repo validate-patches <recipe> after the bump. Results land in
.redbear-recipe-bump/last-report.txt with the line format:
recipe= old= new= patches_total= patches_pass= patches_fail= fail_details=
Failing patch validation is reported, not auto-fixed and never causes a recipe to be removed (per the AGENTS.md ABSOLUTE RULE). Failures become work items for step 4 (stabilization).
The two flags compose:
./local/scripts/bump-release.sh --with-sources --with-external
Step 4 — Rebuild and stabilize
After sources move, the build invalidation machinery fires automatically:
- Source fingerprints — each recipe's content-hash cache (BLAKE3 of build deps) detects that the fork tarball changed and forces a rebuild.
- relibc ABI wipe — when relibc itself moved, every downstream recipe
is rebuilt against the new
libc.a. - Prefix auto-rebuild —
build-redbear.shdetects that relibc/kernel/ base have commits newer thanprefix/.../libc.aand rebuilds the prefix before any recipe build begins. - Preflight gates —
build-preflight.shre-runssync-versions.sh --check,verify-fork-versions.sh,verify-patch-content.py, andverify-collision-detection.pybefore the build proceeds.
Stabilization work then proceeds in the normal build-fix loop: patch
failures surfaced by step 3's report are fixed at the root cause (real
implementation, no stubs — see local/AGENTS.md § "STUB AND WORKAROUND
POLICY"). Lockfile regeneration is a separate explicit operator step:
./local/scripts/sync-versions.sh --regen # after labels + sources settle
Fork decision tree (for --with-sources)
The fork's mode column in local/fork-upstream-map.toml decides what
bump-release.sh --with-sources is allowed to do. Modes currently in use:
| Mode | Meaning | Source-bump eligible? |
|---|---|---|
snapshot |
Imported from an archived upstream snapshot; git history tracks a single upstream tag. (syscall, libredox, redoxfs, redox-scheme, relibc, userutils) | Yes — auto source-bump when upstream semver moves. |
tracked |
Fork follows an upstream ref (e.g. main); content checked but not byte-pinned. (base) |
Yes — but base's map tag is main, so it always resolves to suffix-only (see below). |
diverged |
Fork has diverged materially from upstream; no clean rebase path without operator work. (kernel, bootloader, installer) | No — report-only. Escape hatch: --force-diverged. |
Decision logic, per fork:
mode == snapshot or tracked:
latest_upstream = git ls-remote --tags <url> | latest anchored semver
if latest_upstream > current_base:
action = source-bump (upgrade-forks.sh --to=<latest_upstream>)
else:
action = suffix-only (already done in step 2)
mode == diverged:
action = report-only
# operator may pass --force-diverged to attempt a rebase anyway
tag == main (base special case):
action = suffix-only, always
# base member crates carry no +rb suffix and the verifier accepts that
# (see AGENTS.md § "Version conventions — two categories")
Bootloader note (special case)
bootloader is diverged and additionally has genuinely unrelated git
history — there is no merge-base between the fork HEAD and upstream
master. git merge-base HEAD upstream/master fails. This means
upgrade-forks.sh cannot compute a net-diff to reapply and any attempt to
source-bump it would destroy committed work.
bump-release.sh NEVER attempts a bootloader source bump, even with
--force-diverged. It reports bootloader as action=report-only with the
guidance:
Bootloader fork has no merge-base with upstream. A one-time
git replace --graft <fork-head> <upstream-tag>intervention is required to synthesize a common ancestor before any rebase can be attempted. This is future work; the orchestrator never performs it.
The graft path (documented for the future, not run by any script today):
cd local/sources/bootloader
git replace --graft $(git rev-parse HEAD) <upstream-tag-sha>
# now upgrade-forks.sh can compute a merge-base and reapply the Red Bear delta
Until that graft lands, bootloader stays at its current upstream base and
only its +rb<branch> suffix moves with each release bump.
Notes and invariants
Cargo.toml.orig files are never touched
Every Cat 2 fork may carry a Cargo.toml.orig that mirrors the upstream
Cargo.toml byte-for-byte (Cargo leaves it behind when the fork edits the
real Cargo.toml). sync-versions.sh, bump-release.sh, and the
post-checkout hook all edit ONLY the live Cargo.toml. Cargo.toml.orig
is a read-only witness of upstream state and MUST stay byte-identical to
upstream so that verify-fork-versions.sh can compute the Red Bear delta.
Lockfile regeneration is a separate explicit step
sync-versions.sh --no-regen (the default, and what the hook calls) rewrites
version labels but does not regenerate Cargo.lock. Lockfile regen is
opt-in via sync-versions.sh --regen and should be run after both labels
and sources have settled, because a source rebase can pull new transitive
deps that an earlier label-only regen would miss.
Nothing auto-commits
Every step that changes the tree prints the exact commit sequence:
- Parent repo (
RedBear-OSon the release branch): label bumps, map column-3 updates, recipe tarball/blake3/rev updates. submodule/<fork>branches: fork source rebases.
The operator runs git add / git commit / git push themselves. This is
non-negotiable per local/AGENTS.md § "BRANCH AND SUBMODULE POLICY" and
the project's general "agents never commit unless asked" rule.
Role of provision-release.sh and REDBEAR_RELEASE
provision-release.sh is the freeze-time tool: it provisions a new
immutable release archive from a Redox ref into sources/redbear-<release>/.
It is invoked explicitly and human-initiated, never by the bump pipeline.
REDBEAR_RELEASE=<x.y.z> is the env var that switches the build into
sealed/release mode: sources are extracted from the immutable archive,
online fetching is completely disabled, and local forks are ignored. In
development mode REDBEAR_RELEASE MUST be unset (see .config rules in
AGENTS.md § "BUILD COMMANDS"). The bump pipeline runs in development mode;
it has no behaviour in sealed mode.
Cross-references
| Topic | Where |
|---|---|
Version categories (Cat 1 / Cat 2), +rb suffix rules |
local/AGENTS.md § "Version conventions — two categories" |
| No-fake-version-label rule (labels match source content) | local/AGENTS.md § "No-fake-version-label rule" |
| Latest-upstream-before-freeze rule | local/AGENTS.md § "Local fork dependency rule" |
| Local fork dependency rule (path deps, no version strings) | local/AGENTS.md § "Local fork dependency rule" |
| Branch and submodule policy (operator-only branches) | local/AGENTS.md § "BRANCH AND SUBMODULE POLICY" |
| Tool inventory + modes | local/scripts/TOOLS.md |
| Hook install / bypass / inventory | local/docs/HOOKS.md |
| Fork → upstream tag/mode map | local/fork-upstream-map.toml |
| External desktop-stack version map | local/external-upstream-map.toml |
| Post-checkout hook source | local/scripts/post-checkout-version-sync.sh |
| Installer source | local/scripts/install-git-hooks.sh |
| Orchestrator source | local/scripts/bump-release.sh |
Appendix — full worked example
Bumping from 0.3.1 to 0.3.2 with the hook installed:
# Step 1 — operator creates 0.3.2 on gitea (out of band), then:
git fetch origin
git checkout 0.3.2
# ⟶ post-checkout hook fires:
# post-checkout-version-sync: branch '0.3.2' root Cargo.toml at '0.3.1' -> '0.3.2'
# post-checkout-version-sync: running sync-versions.sh --no-regen (labels only, no lockfile regen)
# ... sync output ...
# post-checkout-version-sync: label sync complete for branch '0.3.2'.
# follow-up steps (NOT run automatically): ...
git add -A && git commit -m "release: sync version labels to 0.3.2"
# Step 3 — source upgrades (operator decides)
./local/scripts/bump-release.sh --with-sources --with-external
# ⟶ per-fork decision lines, per-recipe validation report at
# .redbear-recipe-bump/last-report.txt. Prints commit commands.
# Step 4 — rebuild + stabilize
./local/scripts/sync-versions.sh --regen # lockfiles
./local/scripts/build-redbear.sh redbear-mini # build + preflight gates
# fix any patch fallout at the root cause; commit on 0.3.2 / submodule/<fork>
Without the hook, step 2 becomes:
git checkout 0.3.2
./local/scripts/bump-release.sh # labels + reports
git add -A && git commit -m "release: sync version labels to 0.3.2"
The rest is identical.