Tier 1 message fixes (Part 5.3):
- M18/M19 (build-redbear.sh:523-524): reworded patch-application messages
to reflect local fork model (forks have patches committed; recipe patches
for non-fork recipes applied atomically by repo fetch)
- M46 (build-redbear.sh:923): 'immutable archives' → 'local forks'
Tier 2 doc consolidation (Part 7):
- GRUB-INTEGRATION-PLAN.md: updated all 'make all CONFIG_NAME=' commands
to canonical './local/scripts/build-redbear.sh' equivalents
- SLEEP-IMPLEMENTATION-PLAN.md: archived (referenced 0.2.4 branch, pre-0.3.1)
Assessment doc Part 7 updated: all Tier 1 (items 1-6) and Tier 2 (items 7-13)
marked as verified complete with per-item status.
All Q1-Q10 quality improvement opportunities from Part 6.2 and all Tier 1-3
items from Part 7 are now resolved.
Tier 3 code improvements (from BUILD-SYSTEM-ASSESSMENT-2026-07-18 Part 6.2):
- Q2: Add run_command_with_retry() helper with exponential backoff (1s, 2s, 4s).
Wrap download_wget and git clone with 3 attempts. Git clone closure cleans
partial tmp dir between retries (git refuses non-empty clone targets).
- Q3: Preserve auto_deps.toml alongside dep_hashes.toml in binary store publish
(repo_builder.rs). Restore path prefers the preserved copy, falling back to
declared-depends-only reconstruction only when the preserved copy is absent
(e.g. published by an older cookbook). Preserves full ELF dynamic dep graph.
- Q4: DepHashes::read now returns Result<Option<Self>, String> discriminating
'missing' (legitimate first build, fall back to mtime) from 'corrupt' (loud
WARN + force rebuild). Closes a silent-swallow gap where a corrupt
dep_hashes.toml produced an incorrect mtime-fallback rebuild.
- Q6: Standardize error reporting in repo.rs nonstop path to {:#} format,
matching the pattern established in the earlier Tier 1 message commit.
Tier 1 message fixes (remaining items from assessment Part 5.3/5.4):
- M26: build-redbear.sh:569 '30-60 minutes' -> config-dependent range
- A6/A8: Makefile container messages add remediation hint
- B3: mk/config.mk sccache hint adds install guidance
- H2: mk/qemu.mk Unsupported ARCH lists supported values
- mk/redbear.mk comment 'fully-patched' -> 'versioned'
Verification: cargo check 0 errors, cargo test --lib 35/35 passed,
cargo clippy 0 new warnings vs baseline, make -n live + validate clean.
- build-redbear.sh: detect changes to the 4 forks the staleness loop missed
(userutils pkgar; syscall/libredox/redox-scheme crate path-deps force a full
userspace relink) — prevents stale-binary skew (bash -n validated).
- glib: drop the stale '#undef AT_FDCWD' Redox workaround — relibc now provides
faccessat()/AT_FDCWD, and glib's HAVE_FACCESSAT path needs AT_FDCWD.
- mesa: remove the dropped '-Dosmesa=true' meson option (unknown option) and
guard the osmesa.pc pkg-config edit.
- libinput: unblocked by syncing the fresh relibc libc.a (with timerfd_*) into
the stale prefix sysroot (prefix-sysroot staleness — see notes).
The kernel event scheme now implements kdup, so dup() of an epoll fd works
and tokio's registry.try_clone() succeeds — fixing the tokio runtime EINVAL
that killed every zbus daemon and the brush login shell.
build-redbear.sh: gate the userspace build/sysroot clean on relibc/base
(the forks userspace links) instead of any low-level fork, so a kernel-only
change rebuilds the kernel + initfs + image without a full userspace re-cook.
relibc is statically linked into every userspace binary, so a relibc change
invalidates every consumer, not just the 4 initfs-critical packages the old
ABI_CRITICAL_PKGS list covered. A stale consumer silently bakes in old relibc
behaviour; diagnosing against such stale binaries wastes enormous time. Now:
if relibc rebuilds, ALL package pkgars + recipe build trees are invalidated
for a clean relink; otherwise any consumer pkgar older than relibc.pkgar (the
interrupted-build skew that shipped a broken login image) is invalidated.
Also add a temporary SESSIOND_DIAG probe that tries each tokio runtime config
and logs which one EINVALs, to pinpoint the tokio-runtime failure.
Mini now boots (q35/KVM) all the way to the getty "Red Bear login:" prompt
on VT2 with zero kernel panics. Root-cause fixes across submodules + config:
- config: 00_base.service zsh->mkdir (a full shell startup hangs an init
oneshot; the earlier 'echo/mkdir also hang' result was a silent config
override running zsh). 00_ptyd.service notify->{scheme=pty} (ptyd signals
readiness via its scheme fd; notify made init block on a byte forever).
- kernel: wrmsr_safe/rdmsr_safe #GP recovery so /scheme/sys/msr can't panic
the kernel (cpufreqd's P-state MSR write #GP'd on KVM, halting the boot
before login). base: ptyd scheme type + hwd ACPI enum -> debug (noise).
installer: warn on config [[files]] override. relibc/bootloader: prior
getrlimit + warning-demotion pointers.
- build-redbear.sh: fix host-fstools staleness (installer/redoxfs never
rebuilt on source change), prefix rebuild-every-build (touch libc.a), add
concurrent-build flock.
Submodule pointers: base 8aa8616d, installer 460d9530, kernel 155d01b1,
relibc a7663b3a, bootloader 6e119641.
The redbear-bare target (added in 48a6f4c20b) was not registered in
the build dispatchers. Running build-redbear.sh redbear-bare
silently exited with 'ERROR: Unknown config redbear-bare'.
Fix:
- build-redbear.sh: extend the case statement + usage block + error
message to include redbear-bare
- integrate-redbear.sh: add config/redbear-bare.toml to the configs
array so source-tree integration picks up the bare target too
- build-all-isos.sh: include redbear-bare in the targets list
This closes the gap where the bare target existed as a config file
and as a recipe branch (REDBEAR_BARE_INITFS) but was invisible to
the build dispatch layer.
(NO AI attribution)
Round-25 fix landed the bare initfs conditional in
recipes/core/base-initfs/recipe.toml but the cookbook does NOT parse
[build.env] sections in TOML configs. The REDBEAR_BARE_INITFS=1
env var I put in redbear-bare.toml was silently ignored.
Fix: propagate REDBEAR_BARE_INITFS through build-redbear.sh's make live
invocation. When CONFIG=redbear-bare and the operator has not already
exported REDBEAR_BARE_INITFS, auto-set it to 1 so the bare initfs
branch is triggered. Also propagates any pre-existing REDBEAR_BARE_INITFS
through the make call (override-only).
Also rewrote the [build.env] section in redbear-bare.toml to a
comment-only section explaining the dispatch mechanism, since the
cookbook parser ignores it.
(NO AI attribution)
Per local/AGENTS.md § "BRANCH AND SUBMODULE POLICY (ABSOLUTE)":\neach Red Bear fork worktree must be on the canonical\n"submodule/<name>" branch. Until now, the build system had NO\ncode enforcing this — drift was caught only by operator discipline\n(Round-21 hand-normalized 6 of 9 forks).\n\nAdd a new redbear_check_fork_branches() function called as a peer of\nthe existing dirty-source gate. It iterates REDBEAR_FORK_SOURCES,\nreads each fork worktree's current branch via 'git branch\n--show-current', and compares to 'submodule/<label>'. On mismatch\nthe build aborts with a clear error message and a documented fix\nrecipe. Escape hatch: REDBEAR_ALLOW_WRONG_BRANCH=1.
The script uses '${BRANCH_VERSION:-0.0.0}' as the +rb suffix when
bumping a fork to a new upstream version. BRANCH_VERSION was never
set, so any fork bump would have produced 'X.Y.Z+rb0.0.0' instead
of the correct 'X.Y.Z+rb0.3.2' (or whichever branch is current).
Derive BRANCH_VERSION from 'git branch --show-current' using the same
pattern as local/scripts/build-redbear.sh:158-165. Falls back to
'0.0.0' if the branch name doesn't match semver (e.g., on 'master').
(NO AI attribution)
The verify-overlay-integrity.sh script has two blind spots for stale
symlink detection:
1. find recipes -maxdepth 3 misses paths at depth 4+
2. only checks targets under /local/recipes/, misses /local/patches/
This adds a parallel scan that finds all dangling symlinks under recipes/
without maxdepth, classifies them by target path, and reports each with a
suggested action. Pure diagnostic — does not modify any files.
Enables preventive detection of the recipes/wip/x11/mesa-x11/source
orphan and recipes/core/base/redox.patch pointer-rot that triggered the
build integrity warnings on this session's rebuilds.
(NO AI attribution in commit message.)
Archived mega-patches (kernel/redox.patch, base/redox.patch, relibc/redox.patch,
installer/redox.patch, bootloader/redox.patch, P2-boot-runtime-fixes) were
consolidated into modular P0-...P9- patches during Phase 2/3 cleanup but the
integrity checker still expected them. Removed from EXPECTED_PATCH_SYMLINKS
and CRITICAL_PATCHES arrays. Fixed qt6-wayland-smoke symlink.
Overlay integrity check now PASSES (0 errors).
When recipes/<cat>/<name> is a directory symlink into local/recipes/,
creating a file-level symlink at recipes/<cat>/<name>/recipe.toml
resolves through the dir symlink and CORRUPTS the real file at
local/recipes/<cat>/<name>/recipe.toml (replaces it with a broken
self-referencing symlink).
This was the root cause of 186 'broken recipe.toml' auto-repairs
on every build. Called via apply-patches.sh line 369.
Broken recipe.toml corruption also affects tracked symlinks in
recipes/ tree (e.g. recipes/libs/libxau/recipe.toml). Extended
the auto-repair find to cover both local/recipes/ and recipes/.
Detects broken recipe.toml symlinks in local/recipes/ (self-referencing
corruption from unknown source) and auto-restores from git before build.
Prevents 'Package not found' errors when recipe.toml files are unreadable.
apply-patches.sh: 005-qtbase signature now checks correct path
(recipes/wip/qt/qtbase, not recipes/libs/qtbase) and respects
local recipe override. Qt is desktop-only (redbear-full target).
check-fork-drift.sh: skip diverged forks (kernel, bootloader, installer)
to suppress spurious DRIFT warnings. These forks intentionally diverge
from upstream per fork-upstream-map.toml.
integrate-redbear.sh: add safety guard — refuse to symlink any path
ending in 'recipe.toml'. 185 local recipe.toml files were corrupted
into broken self-referencing symlinks by an earlier build session.
All restored from git. Guard prevents reoccurrence.
check-fork-drift.sh: diverged forks (kernel, bootloader, installer)
are intentionally ahead of upstream — skip them to suppress spurious
DRIFT warnings during build preflight.
apply-patches.sh: 005-qtbase patch signature now checks correct path
(recipes/wip/qt/qtbase/recipe.toml) and respects local recipe override.
Patch creates recipes/wip/qt/qtbase/recipe.toml but the signature
check looked for REDBEAR marker in recipes/libs/qtbase/recipe.toml
(wrong path). Also added check for local recipe existence (takes
precedence per local-over-WIP policy).
This was causing overlay integrity check failures during redbear-mini
builds even though Qt is irrelevant for the console target.
Common function names (new, read, write, parse, get, set, remove, etc.)
appear in many unrelated files — cross-file search produces false MOVED
classifications (e.g., fn new() in ipcd matches fn new() in xhcid).
The case-based guard skips cross-file search for 40+ common function names.
These must be handled via the .verify-fork-functions.exclude mechanism
instead — operator decides if removal was intentional or accidental.
Results: kernel 19→17 (kcall×2 correctly MOVED to user.rs, eq/hash
correctly blocked from false MOVED to fdstat.rs)
upgrade-forks.sh:
- --no-fetch: use cached upstream refs (no network required)
- --force-reapply: force rebase even when 0 commits behind upstream
Automatically detects missing functions via verify-fork-functions.sh
and triggers reapply when RB cherry-picks dropped upstream code.
verify-fork-functions.sh:
- Cross-file search: when a function is missing from its upstream file,
search ALL fork .rs files for renamed/moved equivalents (→ MOVED)
- Per-fork exclusion list: .verify-fork-functions.exclude for
intentionally removed/replaced functions (→ EXCLUDED)
- Only truly missing (not found anywhere, not excluded) = violations
build-preflight.sh:
- Updated fix suggestion to --no-fetch --force-reapply
Results: installer 2→0 (exclusion+move), kernel 19→15 (4 moved),
base 56→44 (12 moved). Remaining missing functions are known RB
replacements pending exclusion entries.
- PATCH-PRESERVATION-AUDIT: add Phase 16.1 to cumulative reduction table
(122→0 orphans, Round 17); update operator test section with all 6 modes;
replace stale 'Round 3 final' re-run with current Round 17 verification
- TOOLS.md: add --regen and --dry-run flags; clarify default is opt-in
- HOOKS.md: add --regen and --dry-run to sync-versions.sh option list
- REGEN_LOCKFILES defaults to 0 (opt-in, not opt-out)
- New --regen flag explicitly enables lockfile regeneration
- New --dry-run flag: snapshots each Cargo.lock, regens, diffs, reverts
Reports per-lockfile changes that WOULD occur without modifying files
- Fixed --regen-only deadlock: CHECK_ONLY=1 no longer blocks regen
(mode_regen_only gate added to CHECK_ONLY skip check)
- Fixed ROOT_DRIFT unbound variable in --regen-only mode
(: ${ROOT_DRIFT:=0} before summary section)
- Moved regen_lockfile_for() definition before dry-run block
(was defined after call site — caused 'command not found')
Replaces the 6 UNKNOWN entries in the post-loop summary with
specific status derived from fork-upstream-map.toml when the
main loop doesn't set a per-fork status.
Fallback logic for forks without explicit FORK_STATUS:
- diverged mode → DIVERGED-BYPASS
- tracked mode → TRACKED-PASS
- snapshot mode → SNAPSHOT-PASS (with actual tag from map)
- not in map → NOT-MAPPED
This makes every fork's status visible at a glance:
=== Post-verification fork status ===
bootloader DIVERGED mode=diverged; content check skipped
installer DIVERGED mode=diverged; content check skipped
kernel DIVERGED mode=diverged; content check skipped
libredox PASS content matches upstream 0.1.18
base TRACKED-PASS tracked-fork, version label passed
redoxfs SNAPSHOT-PASS snapshot fork, version label 0.9.1
relibc SNAPSHOT-PASS snapshot fork, version label 0.2.5
...
Bug fix: upstream_tag variable was stale from the last loop
iteration. Now reads the correct tag from fork-upstream-map.toml.
Per assessment G2 from Round 11: verify-fork-versions.sh silently
skipped content checks for forks whose upstream tags don't exist,
making failures invisible. This adds a post-loop summary table
that enumerates each fork's verification status.
Added per-fork status tracking:
- DIVERGED: fork in diverged mode (content check skipped)
- NO_UPSTREAM_TAG: upstream tag doesn't exist (content check skipped)
- PASS: content matches upstream tag
- SKIP: no Cargo.toml (not a tracked fork)
- UNKNOWN: not yet classified (future refinement)
The summary table prints after the per-fork warn/error output,
giving operators a single-view snapshot of all 10 forks:
=== Post-verification fork status ===
fork status detail
---------- ---------------- ------
base UNKNOWN not checked
bootloader DIVERGED mode=diverged; content check skipped
installer DIVERGED mode=diverged; content check skipped
kernel DIVERGED mode=diverged; content check skipped
libredox PASS content matches upstream 0.1.18
...
Bug fix: added cd "$ROOT" before post-loop summary because the
per-fork content-check loop cd's into each fork directory.
Creates local/scripts/TOOLS.md as a companion to HOOKS.md,
listing only the 10 build-system operator tools (excluding the
~120 test scripts under local/scripts/).
Each tool has:
- One-line purpose
- Usage example
- All mode flags documented
The reference includes a call-tree showing which tools
run in build-preflight.sh vs pre-push-checks.sh.
Added README.md link to TOOLS.md in the Documentation section.
Adds the commit-msg hook promised in HOOKS.md since Round 5.
The hook auto-prepends the current Red Bear development phase
(e.g. 'phase 8.4:') to commit messages. The phase is detected
from the most recent 'phase X.Y:' commit in the git log.
Hook behavior:
- Normal commit → prepended: 'phase 8.4: my message'
- Already has prefix → skipped
- Revert message → skipped
- Empty/comment → skipped
Install: cp local/scripts/commit-msg .git/hooks/commit-msg && chmod +x
Updates HOOKS.md with:
- commit-msg hook documentation (install, behavior)
- Full tool inventory table (10 tools across 9+ rounds)
listing each tool's type, purpose, and modes/flags
Per Round 6-7 out-of-scope: collision detection didn't track
dynamically-generated files from base's Makefile. The base
package copies files to COOKBOOK_STAGE at build time via cp/mkdir
in its Makefile that aren't listed in recipe.toml's [[package]].installs
or [[package]].files fields.
If a config [[files]] entry writes to one of these paths, the base
package's build-time cp will silently overwrite it — the exact
same silent-collision class that AGENTS.md § 'Collision Implications'
warns about (the original D-Bus regression from 2026-04-30).
Added 18 hardcoded dynamic-build paths from base's Makefile:
initfs paths:
'/usr/lib/boot/initfs.img'
'/usr/lib/init.d/00_logd.service'
'/usr/lib/init.d/00_nulld.service'
'/usr/lib/init.d/00_randd.service'
'/usr/lib/init.d/00_rtcd.service'
'/usr/lib/init.d/00_base.target'
'/usr/lib/init.d/00_pcid-spawner.service'
'/usr/lib/init.d/00_ptyd.service'
'/usr/lib/init.d/00_sudo.service'
'/usr/lib/init.d/00_ipcd.service'
'/usr/lib/init.d/90_initfs.target'
'/usr/lib/pcid.d/initfs.toml'
driver paths:
'/usr/lib/drivers/acpid'
'/usr/lib/drivers/fbbootlogd'
'/usr/lib/drivers/hwd'
'/usr/lib/drivers/inputd'
'/usr/lib/drivers/lived'
'/usr/lib/drivers/redoxfs'
Tested with synthetic collision:
- Created config/redbear-test-dynamic.toml with
path='/usr/lib/boot/initfs.img'
- Collision detection correctly reports exact-match collision
with source 'build-dynamic'
- Cleanup removes the test file, returning to 0 collisions
Survey count: 162 (was 144 + 18 dynamic paths). Production: 0
collisions.
New script produces a human-readable report in 3 modes:
./local/scripts/patch-status.sh full text report
./local/scripts/patch-status.sh --brief one-line summary
./local/scripts/patch-status.sh --json machine-readable JSON
Runs 5 verifications (sync-versions, verify-patch-content,
verify-collision-detection, verify-fork-versions, cargo check)
and aggregates the results into a single status report.
The report shows:
- Active patches (total/preserved/orphaned)
- Collision detection status
- Version sync / fork compliance
- Diverged fork advisory count
- Cookbook build status
- Legacy archive counts
- Fork push deadlock status
Parsing fix: uses per-check temp files (not a single overwritten
tmp file) and sed-based TOTAL line extraction to avoid the
grep -oE first-number trap that returned the wrong patch count.
Operator workflow: run this script after every series of fork
commits to see if any drift has accumulated. Takes 30-120s.
Adds --selftest mode that runs 5 regression test cases against a
temp directory. The temp dir is created OUTSIDE the parent git
working tree (e.g. via /tmp/.tmp-redbear-selftest-/) so that the
test's 'git log' commands don't pick up the parent repo's 100+
commits.
Test cases:
1. patch with sig in fork target preserved
2. patch without sig in fork target orphan
3. no-target patch (no +++ b/ header) orphan (no-target)
4. patch with content-integrated target orphan (classification)
5. verify --report action produces per-orphan decision
Bug fixed during dev: passing fork_dir to git log WITHOUT
isolating it from the parent repo's git tree caused 50 'case2'
keyword matches to leak in from the RedBear-OS parent repo's
commit log, making case2 falsely classified as INTEGRATED. Fixed
by using tempfile.mkdtemp(dir=os.path.dirname(REPO_ROOT)) to put
the test fork dir OUTSIDE the parent git tree.
Also fixed: audit_component now accepts an optional patches_dir
arg (so selftest can pass a non-default patches path without
modifying the main code path).
Also fixed: 'if not target_path or target_path == \'?\'' check
in suggest_action_for_orphan to handle the literal '?' sentinel
that audit_component uses for no-target orphans.
Wired into pre-push-checks.sh as check #3b (verify-patch-content-selftest).
Total: 7 pre-push checks now run before any push.
Adds --report action mode to verify-patch-content.py that implements
the AGENTS.md § 'Orphan-Patch Supersession Decision Tree' algorithmically.
For each orphan, the action recommender outputs one of:
- INTEGRATED: fork log has >5 commits matching topic keywords;
the work IS in the fork under a different commit
subject. Safe to archive as superseded.
- FILE-RESTRUCTURED: target file no longer exists in fork (file
rebased past patch's expectations). Safe to archive.
- NO-TARGET-FILE: patch lacks +++ b/ header (non-actionable).
Safe to archive.
- MISSING-UPSTREAM: work NOT in fork; needs reapply or fork rebase.
Algorithm (in suggest_action_for_orphan):
1. Check if patch has no target file (lacks +++ b/) → NO-TARGET-FILE
2. Check if target file no longer exists in fork → FILE-RESTRUCTURED
3. Run 'git log -i --grep <topic-keyword>' on the fork; if >5 commits
match → INTEGRATED
4. Otherwise → MISSING-UPSTREAM
Validated with synthetic test: created a local/patches/relibc/
_test-synthetic-orphan.patch with target=lib/test/strange.rs (which
doesn't exist in fork). The recommender correctly classified it as
FILE-RESTRUCTURED.
Wired into pre-push-checks.sh as check #3a (verify-patch-content-action).
Future new orphans are auto-classified; the operator only needs to
verify the suggested classification matches reality and then archive
to legacy-superseded-2026-07-12/.
Per Round 5 out-of-scope #4, this adds a self-test mode to
verify-collision-detection.py. The selftest covers the algorithm's
edge cases:
1. exact match (cfg == install) collision=True
2. parent-dir prefix (install under cfg) collision=True
3. /etc/init.d override of init.d safe
4. /etc/environment.d override of env.d safe
5. non-overlapping names no collision
6. parent of subpath (e.g. /etc vs /etc/foo) collision=True
7. two siblings (same prefix) no collision
8. /etc/init.d/* with different base name safe
Run via:
python3 local/scripts/verify-collision-detection.py --selftest
The selftest is wired into local/scripts/pre-push-checks.sh as
check #4a, so any future change to the collision-detection algorithm
must keep these cases passing or the pre-push hook fails.
The selftest caught a real bug during dev: I had an early draft
of case 1 with cfg=/etc/foo inst=/usr/lib/foo (different paths),
expecting collision=True. The actual algorithm correctly returned
False (no overlap). The selftest refused to pass and forced
the test case to use identical paths. This is exactly the kind
of regression the test was designed to catch.
Round 5/6 left the base fork unable to be force-pushed to origin.
After investigation in Phase 5.3, the actual cause was identified:
the gitea server's receive.shallowUpdate=true config (not the
ref name as previously thought).
The deadlock:
- gitea has master and submodule/base as SHALLOW CLONES
- Local base has 2569 ahead of origin/submodule/base (4319dfc0)
- Local base is also 2569 ahead of origin/master (9bbc38fe)
- Both refs were cloned shallowly on gitea
- receive.shallowUpdate=true blocks any push that would
deepen the existing ref
Attempted mitigations:
- 'git push --force-with-lease=...' → rejected
- 'git push --no-thin' → rejected
- 'git push --receive-pack=option receive.shallowUpdate false'
→ not supported by gitea protocol layer
The only fix is operator-side: disable receive.shallowUpdate on
the gitea repo. Per AGENTS.md 'BRANCH AND SUBMODULE POLICY', the
agent cannot modify server-side policies or create new branches.
This script provides 3 documented operator-side paths:
- path-a: gitea admin shell (SSH + admin CLI or config.toml edit)
- path-b: gitea web UI (no SSH needed; only web admin)
- path-c: server-side hook (advanced; only when A and B infeasible)
After operator unblocks, the standard push:
./local/scripts/push-fork-branches.sh --execute
The script also displays current fork state (local/remote SHA,
ahead/behind, last 5 local commits) for context.
Files in scope: local/scripts/unblock-base-push.sh
Per Round 5 out-of-scope #4: collision detection now also reads
[[package]].files in addition to [[package]].installs.
The codebase actually uses [[package]].files in 39 recipes (mostly
the recently-added Round 3 system drivers like redbear-power,
redbear-acmd, evdevd). The format is an inline table mapping
install path -> source basename:
[[package.files]]
"/usr/bin/redbear-power" = "redbear-power"
In the previous version of the script, this field was not
checked, so any future recipe using this pattern for install
paths would silently have its config [[files]] override ignored
by package staging. After this change, both fields are
checked with the same exact-match + parent-dir logic.
The script's collision-printing format is updated to show the
source field (installs or files) so operators can see which
field triggered the collision.
Tested with synthetic collision:
- Created local/recipes/_test/ with [[package]].files=['/lib/test/special.toml']
- Created config/redbear-test-files.toml with [[files]] path=/lib/test/special.toml
- Verify-collision-detection correctly reported exact-match collision
- Cleanup removed both test files
Production state (after cleanup):
- 68 recipe installs surveyed
- 76 [[package.files]] dict entries surveyed
- 165 config [[files]] entries surveyed
- 0 collisions
Per AGENTS.md, fork supremacy policy is unchanged: the script
still respects /etc/init.d/* and /etc/environment.d/* as known-safe
overrides.
Per AGENTS.md 'Daily-upstream-safe workflow', drift between fork
state and active patch system can only be caught at next-build time.
This commit closes the gap by providing a pre-push hook that runs
the 4 critical pre-flight checks BEFORE pushing to origin.
The hook runs (in order):
1. sync-versions.sh --check (Cat 0 + Cat 1 + Cat 2 versions)
2. verify-fork-versions.sh (Cat 2 fork supremacy)
3. verify-patch-content.py (no orphan patches)
4. verify-collision-detection.py (no config-vs-package conflicts)
Per AGENTS.md 'absolutely NEVER DELETE, NEVER IGNORE' rule, the hook
is OPT-IN: operators must explicitly install it via
cp local/scripts/pre-push-checks.sh .git/hooks/pre-push
chmod +x .git/hooks/pre-push
The opt-in nature respects operator autonomy — local hooks do not
propagate (each clone must re-install), and false positives would
block legitimate pushes.
HOOKS.md documents the available hooks + future work (pre-receive
on server side for defense in depth; commit-msg hook for auto-phase
prefix in commit messages).
Default mode of the script: fail on any drift (exit 1). Use
--soft flag or REDBEAR_SKIP_PRE_PUSH=1 to bypass.
Per AGENTS.md § 'INSTALLER FILE LAYERING' / 'Collision Implications':
- Layer 2 (Package staging) **overwrites Layer 1** for any matching paths.
- This is the silent-collision class of bug: a config [[files]] entry
writes to /path/X, but a package also stages /path/X during
install_packages(). Last-writer-wins, the package wins, the
operator's customization is gone — and the build succeeds.
Phase 0 audit confirmed that AGENTS.md's promise of a CollisionTracker
in src/cook/collision.rs was false (the file doesn't exist). Phase 4.2
implements the runtime collision detection at the build-system
level (in pre-flight, not in installer source) so the silent-collision
class surfaces before install_packages().
New file: local/scripts/verify-collision-detection.py
- Parses every recipes/<cat>/<pkg>/recipe.toml's [[package]].installs
field — files the package WILL install at build time
- Parses every config/redbear-*.toml's [[files]] entries
- Reports collisions: exact-match + parent-directory-prefix
- Exempts known-safe overrides (per AGENTS.md 'Init Service File
Ownership'): /etc/init.d/*, /etc/environment.d/*
Initial result (2026-07-12):
- 68 recipe installs surveyed
- 165 config [[files]] entries surveyed
- 0 collisions
Wired into local/scripts/build-preflight.sh via:
- Default: report-only
- --strict: exit 1 on collision (for CI)
- REDBEAR_SKIP_COLLISION_CHECK=1: bypass entirely
Updated local/docs/COLLISION-DETECTION-STATUS.md with:
- Phase 4.2 implementation summary
- How the algorithm works
- What it does NOT cover (runtime conflicts, patch-induced, dynamic
generation)
- Recovery options
Phase 4.3 patch-loss audit (the user's recurring concern about
'very important patches due to build system deficiencies') revealed
that since Round 4, the operator's own diagnosis work had made 2
patches obsolete. Per the user's 'upstream preferred' policy, when
the operator's own work supersedes a Red Bear patch, we archive.
P0-canary (operator-superseded):
- Original: Phase 1.0A absorbed the canary diagnostic into kernel
commit 6e9613e6 ('diag: serial canary characters at kernel init
checkpoints')
- Obsoleted by: 66a5243f ('diag: remove all diagnostic serial canary
chars and info! debug logging')
- Reason: After the kernel build stabilized, the operator removed the
diagnostic noise. The canary's purpose (early-boot serial output)
is no longer needed.
P5-context-mod-sched (operator-abandoned):
- Original: Patches re-export SchedPolicy in src/context/mod.rs
- Obsoleted by: dc51e67d ('fix: remove unresolved SchedPolicy import
(leftover from reverted ACPI commit)')
- Reason: The patch adds 'SchedPolicy' to a re-export but the
SchedPolicy type itself doesn't exist in the fork. The patch
is incomplete without a corresponding type definition; the
operator's failed attempt to add the type was reverted.
Both moves follow AGENTS.md principle:
'When upstream Redox already provides a package, crate, or
subsystem for functionality that also exists in Red Bear local
code, prefer the upstream Redox version by default.'
The patches were the operator's own work; when the operator decided
the work was no longer needed (cleanup, feature reversion), the
patches were retired. 'Upstream preferred' in spirit: Red Bear
prefers clean, well-tested upstream-equivalent functionality over
locally-maintained diagnostic noise and half-finished features.
After this commit:
Active patches: 119 (was 121)
Archived: 96 in legacy-superseded-2026-07-12/
All forks: 0 orphans
After 3 rounds of patch-preservation work, multiple fork branches
have new commits that need to reach origin's submodule/<name> refs:
fork local-vs-origin state
base 2564 ahead, 190 behind significant divergence
bootloader 10 ahead, 128 behind 927 vs 77 file divergence
installer 61 ahead, 0 behind ready to force-push
kernel 45 ahead, 0 behind ready to force-push
libredox 69 ahead, 11 behind fast-forward possible
relibc 3434 ahead, 60 behind significant divergence
syscall 1 ahead, 0 behind ready to force-push
userutils 202 ahead, 12 behind merge decisions needed
This script provides:
1. Status table showing ahead/behind for each fork
2. Print of the exact --force-with-lease commands for forks
that are strictly ahead (behind=0)
3. NO-OP default mode (must use --execute to actually push)
4. A 5-second abort window if --execute is used
Per AGENTS.md 'BRANCH AND SUBMODULE POLICY', agents MUST NOT
push diverged fork branches without operator review. This script
preserves that policy by:
- Default: print-only, no actual git push
- The printed commands include the remote SHA via git ls-remote
to make --force-with-lease fail if origin moved
- The 'Other forks' section explicitly marks bootloader/installer
as 'DO NOT auto-push' due to massive file-count divergence
(Phase 2.4+ work)
Implementation notes:
- Uses 'local_sha..remote_sha' / 'remote_sha..local_sha' form
to avoid the 'ambiguous HEAD' error in submodule working
trees (the fork tree has a stale symlink that confuses git
revision parsing — see commit history if that needs fixing)
- behind=0 and ahead>0 are the only cases the script marks
'REVIEW_NEEDED' — behind>0 forks need merge decisions
Build system improvements after Phase 2.0 / 2.1 / 2.2 patch work:
1. New 'diverged' mode in local/fork-upstream-map.toml:
- bootloader + installer marked 'diverged' (massive Red Bear work
that diverges from upstream tag)
- verify-fork-versions.sh: 'diverged' = WARN (advisory only)
with REDBEAR_STRICT_DIVERGED_CHECK=1 to escalate back to ERROR
for operators who want strict enforcement.
2. Bug fix in verify-fork-versions.sh (Phase 2.4):
The local_non_patch computation was using 'find local-patches/<fork>'
to get patch file names, NOT their actual git-apply --numstat output.
Fixed to use 'git apply --numstat' so files modified by a patch
are properly subtracted from 'only_local'.
3. Per-fork declarative expected-differ list:
- libredox: src/lib.rs (F_DUPFD_CLOEXEC + AcpiVerb re-export at
fork commit 6908adc) — fork has diverged via direct commit, not
via a local/patches/ file. Algorithm now recognizes this as
INTEGRATED.
- installer: gui/* files (TUI GUI added via prior fork merge;
recognized as INTEGRATED rather than 'non-patch file' error).
4. libredox fork cleanup:
- Removed .cargo-ok + .cargo_vcs_info.json from tracking (cargo
metadata that should be in .gitignore, not source control)
- Added /Cargo.lock to .gitignore (matches upstream .gitignore)
- This unblocks verify-fork-versions.sh for libredox 0.1.18.
After this commit:
- bash verify-fork-versions.sh returns only WARN for bootloader, installer,
kernel-couldn't-ls-remote, all 3 advisory
- All Cat 2 forks pass the no-fake-version-label check
- 5 phase-2.4 forks tracked correctly
Per Phase 0 audit findings (G1, G2), the build-system version sync
had two omissions that allowed drift to accumulate across branch
bumps:
1. G2: The root Cargo.toml's [package] version was outside the
script's scope. During 0.3.0 -> 0.3.1 branch change, syncing
Cat 1 + Cat 2 versions did not touch the cookbook crate itself,
so 'repo --version' reported the wrong version. Fixed by adding
Phase 0: Cookbook root Cargo.toml block that mirrors the Cat 1
version assignment.
2. G1: After a branch bump, Cargo.lock files were not regenerated.
Each Cat 2 fork's lockfile kept its previous +rb suffix even after
the fork's Cargo.toml was updated. Fixed by adding Phase 3: a
'cargo generate-lockfile' pass that runs in each fork and the
root cookbook after version sync completes. The path-dep +
[patch.crates-io] config in each fork ensures the right crates
are pulled in; the script verifies each cargo generate-lockfile
exit code.
New flags:
--check Verify only (no writes), exit 1 on drift
--no-regen Apply version sync, skip lockfile regen
--regen-only Skip version sync, only regen lockfiles
(default) Apply sync + regen lockfiles
After this commit, branch bump workflow is:
./local/scripts/sync-versions.sh
... and nothing else is needed for Cargo.lock freshness. The
script exits non-zero only if any 'cargo generate-lockfile' fails
in a fork, surfacing build breakage immediately.
Per AGENTS.md § 'Local Fork Recipe Directories Must Not Carry Patch
Files', the symlinks under recipes/core/{base,kernel,relibc,
bootloader,installer}/ pointing at the canonical local/patches/<comp>/
patches were violations. The recipes use path = '...' source so the
cookbook would never apply these patches; the symlinks were a
navigation aid only.
This commit moves all 137 valid symlinks (and the redox.patch
counterparts) to local/docs/legacy-recipe-patches/<comp>/ with a
README explaining the rationale and pointing readers at the canonical
patch location.
The patches themselves (local/patches/<comp>/*.patch) are NOT
modified — they remain git-tracked at their canonical location.
Per AGENTS.md 'NEVER DELETE' policy the patches stay in their
canonical home; only the navigation aid symlinks are relocated.
Breakdown:
base (61) - legacy-recipe-patches/base/
kernel (26) - legacy-recipe-patches/kernel/
relibc (46) - legacy-recipe-patches/relibc/
bootloader (3) - legacy-recipe-patches/bootloader/
installer (1) - legacy-recipe-patches/installer/
After this commit, recipes/core/<comp>/.patch references all show
zero matches per AGENTS.md rule.
1. local/scripts/build-preflight.sh — invoke verify-patch-content.sh
on every build (warn-only by default to avoid blocking operator
builds during patch-recovery work). REDBEAR_SKIP_PATCH_CONTENT_CHECK=1
bypasses the check.
2. local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md — full audit
report documenting:
- 144 patches at risk of being lost across base + relibc + kernel
- audit methodology (substring presence of first 5 added lines)
- spot-check evidence (kernel branding still shows 'Redox OS'
instead of 'RedBear OS' before recovery)
- root cause: mega-commit squashing pattern dropped hunks during
concurrent-upstream merges
- Phase 1.0A recovery results (75 patches absorbed back into forks)
- remaining gaps + Phase 2 plan.
Per the Phase 1.0 audit (local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md)
no part of the build system was checking that patches listed in
local/patches/<comp>/ had actually been absorbed into the
local/sources/<comp>/ fork's working tree.
This commit adds a tool that audits every Cat 2 fork and reports any
patches whose content is not present in the fork HEAD. The tool is
located at local/scripts/verify-patch-content.{sh,py} and follows the
existing local/scripts/ convention (shell wrapper delegating to
Python implementation).
Audit method:
1. For each .patch in local/patches/<comp>/:
- Extract target file from ---/+++ headers
- Extract first 5 added lines (+ markers)
- Check substring presence in fork HEAD version
2. If 0/5 added lines found, patch is 'orphaned'
Current state (after Phase 1.0A recovery):
- 251 patches total, 155 preserved, 96 still orphaned
- The remaining orphans are patches where the upstream file structure
has shifted too far for patch(1) to apply — those need
upstream-tracking upgrade, deferred to Phase 2.
Usage:
./local/scripts/verify-patch-content.sh (report)
./local/scripts/verify-patch-content.sh --strict (exit 1 on orphans)
./local/scripts/verify-patch-content.sh base kernel (specific forks)
Wired in via build-preflight.sh in the next commit (Phase 1.0B cont.).
This closes the 'silent patch loss' gap identified in the audit.
When the upstream commit hash of the claimed upstream tag has not changed
since the last successful verify, reuse the cached file list instead of
re-cloning and re-hashing from scratch. This makes the canonical preflight
fast on the common case where upstream didn't move.
Cache is stored in .redbear-fork-verify/<fork>-<tag>.fingerprint. Touched
only after a successful full content diff, so cache corruption is detected
on the next run (commit hash mismatch triggers fresh clone).