# 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: ```bash 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`): ```bash 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 (`--regen` is 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: ```bash ./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: ```bash 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` ```bash ./local/scripts/bump-release.sh --with-sources ``` For each fork in `local/fork-upstream-map.toml`, `bump-release.sh`: 1. **Classifies** the fork by its map `mode` column (decision tree below). 2. For eligible forks (mode `snapshot` or `tracked`), `git ls-remote --tags` the upstream URL, find the latest anchored-semver tag, and compare to the fork's current base (the `` before `+rb`). 3. If upstream moved: invoke `upgrade-forks.sh --to= `, which fetches + pins `upstream_ref` to `refs/tags/`, creates a backup branch (`rb-backup/`), resets, re-applies the Red Bear net-diff, falls back to cherry-pick if needed, regenerates the fork lockfile, and runs `verify-fork-functions.sh`. 4. On success: set the fork `Cargo.toml` `version = "+rb"`, update map column 3 in place, and print the exact commit commands for the parent repo and the `submodule/` branch. 5. 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` 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` ```bash ./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 ` 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: ```bash ./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.sh` detects that relibc/kernel/ base have commits newer than `prefix/.../libc.a` and rebuilds the prefix before any recipe build begins. - **Preflight gates** — `build-preflight.sh` re-runs `sync-versions.sh --check`, `verify-fork-versions.sh`, `verify-patch-content.py`, and `verify-collision-detection.py` before 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: ```bash ./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 | latest anchored semver if latest_upstream > current_base: action = source-bump (upgrade-forks.sh --to=) 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 ` 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): ```bash cd local/sources/bootloader git replace --graft $(git rev-parse HEAD) # 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` 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-OS` on the release branch): label bumps, map column-3 updates, recipe tarball/blake3/rev updates. - **`submodule/` 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-/`. It is invoked explicitly and human-initiated, never by the bump pipeline. `REDBEAR_RELEASE=` 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: ```bash # 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/ ``` Without the hook, step 2 becomes: ```bash 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.