99e5641127
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
369 lines
15 KiB
Markdown
369 lines
15 KiB
Markdown
# 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 `<X.Y.Z>` before `+rb`).
|
||
3. If upstream moved: invoke `upgrade-forks.sh --to=<tag> <fork>`, which
|
||
fetches + pins `upstream_ref` to `refs/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
|
||
runs `verify-fork-functions.sh`.
|
||
4. On success: set the fork `Cargo.toml` `version = "<tag>+rb<branch>"`,
|
||
update map column 3 in place, and print the exact commit commands for
|
||
the parent repo and the `submodule/<fork>` 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<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`
|
||
|
||
```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 <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:
|
||
|
||
```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 <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):
|
||
|
||
```bash
|
||
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-OS` on 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:
|
||
|
||
```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/<fork>
|
||
```
|
||
|
||
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.
|