Files
RedBear-OS/local/docs/RELEASE-BUMP-WORKFLOW.md
T
vasilito 99e5641127 feat: release-bump pipeline + external graphics version sync
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
2026-07-18 14:45:41 +09:00

15 KiB
Raw Blame History

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 24 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.10.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:

./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:

  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

./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-rebuildbuild-redbear.sh detects that relibc/kernel/ base have commits newer than prefix/.../libc.a and rebuilds the prefix before any recipe build begins.
  • Preflight gatesbuild-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:

./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-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:

# 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.