The usb-kbd behind the QEMU usb-hub now enumerates fully: debounce ->
port reset -> enable -> second debounce -> pre-attach -> attach ->
ATTACH 5.2 -> slot 3 -> addressed -> descriptors read -> HID class match
-> 'Loading subdriver "USB HID" for port 5.2' (class 3.1 proto 1,
keyboard boot protocol).
Root cause of the final stall: an unconditional SetPortFeature
(PORT_INDICATOR) for every connected+enabled port NAK-hung on QEMU's
indicator-less hub (no control-transfer timeout), blocking the flow
before the post-enable debounce. Now gated on
wHubCharacteristics.HUB_CHAR_PORTIND like Linux has_indicators.
test-usb-hub-qemu.sh: check #5 now requires the hub-child subdriver
line ('Loading subdriver "USB HID" for port <root>.<child>') so a
root-port HID (tablet) can no longer satisfy it — closes a false-
positive hole. Timeout raised to 360s for slow TT-path driver bring-up.
Docs (plan P3-A runtime validation + matrix): full hub + hub-child
chain recorded as QEMU-proven; remaining items (intermittent reactor
startup race, slow TT-path Evaluate Context) noted.
Hub enumeration is quick, but a hub-child's class driver then performs
its own descriptor/control transfers on the slow emulated TT path, and
the guest clock runs several times slower than wall time under load.
180s cut the boot before a hub-child HID driver could finish
registering after being spawned.
Serial output from dozens of concurrent daemons interleaves mid-line,
so full-sentence matches like 'usbhubd: 8 port(s) detected' fail even
when the content is present (observed mangled as 'usbhubd: 548: port(s)
detected'). Match short fragments ('port(s) detected', 'change
detection', 'registered producer') that survive interleaving.
The 'USB HUB driver spawned' log line is emitted before setup_logging
and never reaches the serial console, so check #2 could never pass.
Use the hub-descriptor read line (emitted only after XhciClientHandle
open + 8-port descriptor fetch succeed) as the spawn proof, and fold
the old duplicate descriptor check into it.
Cat 1 discovery treated every local/recipes/*\/source/Cargo.toml as an
in-house crate, including git-fetched upstream sources like brush
(git = github.com/reubeno/brush). Rewriting upstream crate versions to
the branch version broke upstream-internal version requirements
(brush-shell was forced to 0.3.1 while brush required ^0.4.0 —
'failed to select a version for the requirement brush-shell = ^0.4.0').
Guard: skip any crate whose Cargo.toml is not tracked by the parent
repo. In-house crates are tracked trees (no independent .git); fetched
sources are gitignored and untracked. Restores correct classification:
75 crates checked (12 fetched-source crates now excluded), drift 0,
brush cooks successfully again.
- sync-versions.sh: 11 brush workspace crates 0.x -> 0.3.1 (Cat 1
in-house version policy).
- test-usb-hub-qemu.sh: boot the live ISO (canonical build-redbear.sh
artifact) instead of harddrive.img, which is only produced by older
make-all flows and goes stale — a stale Jul-17 image with a
pre-version-sync bootloader (+rb0.3.0) masked the current build and
crashed the first proof attempt. Falls back to harddrive.img only
when no ISO exists.
The script cd's into the fork dir for the clean-tree check (line ~202)
and never returns to PROJECT_ROOT. Every subsequent relative
'cd "$fork_dir"' subshell then fails with 'No such file or directory',
making single-fork and multi-fork runs always FAIL at the upstream-ref
resolution step. Make fork_dir absolute at assignment so all subshell
cd/git calls work regardless of current directory.
Found while attempting the base fork sync (verify-fork-functions gate).
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