diff --git a/local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md b/local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md new file mode 100644 index 0000000000..ab2e081167 --- /dev/null +++ b/local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md @@ -0,0 +1,160 @@ +# Fork Patch Preservation Audit — 2026-07-12 + +## CRITICAL FINDING — 144 patches at risk of being lost + +Across all 9 Cat 2 fork components, patches exist in `local/patches//` +whose content is NOT preserved as commits in the corresponding +`local/sources//` fork. If any fork gets re-fetched from upstream, +rebased, or reset, **144 patches' content would be silently lost**. + +## Audit Method + +For each patch in `local/patches//`: +1. Extract target file path from the `+++ b/...` header. +2. Extract first 3-5 added lines (`+X` markers, excluding `+++`/`@@`). +3. Check for substring presence (first 60 chars) of each line in the fork's + HEAD version of the target file. +4. If at least one added line is found: **preserved** in fork. +5. If none found: **orphaned** (the patch exists but the change is not in fork). + +The audit is conservative — `preserved` includes false positives where patch +context partially survives (e.g., identical-looking boilerplate). `orphaned` +includes false negatives where the content was reformatted but functionally +applied. Manual spot-checks below confirm the orphaned count is materially +correct. + +## Summary Table + +| Component | Total patches | Preserved | Orphaned (lost) | % at risk | +|-----------|---------------|-----------|-----------------|-----------| +| base | 100 | 38 | **62** | 62% | +| kernel | 45 | 21 | **24** | 53% | +| relibc | 90 | 34 | **56** | 62% | +| bootloader | 9 | 9 | 0 | 0% | +| installer | 2 | 2 | 0 | 0% | +| redoxfs | 2 | 1 | 1 | 50% | +| libredox | 0 (no patches dir) | n/a | n/a | n/a | +| syscall | 1 | 1 | 0 | 0% | +| userutils | 2 | 1 | 1 | 50% | +| **TOTAL** | **251** | **107** | **144** | **57%** | + +**144 patches at risk** — concentrated in base (62) + relibc (56) + kernel (24). + +## Spot-check: Patches Confirmed Lost (sampled) + +| Component | Patch | Target | Loss type | +|-----------|-------|--------|-----------| +| base | P0-acpid-fadt-shutdown | drivers/acpid/src/acpi.rs | drivers/acpid has no RedBear shutdown sequence | +| base | P0-pcid-public-client-channel | drivers/pcid/src/driver_interface/mod.rs | reconnect logic absent | +| base | P4-initfs-dbus-services | init.d/05_boot_essential.target | file missing | +| base | P4-initfs-usb-drm-services | init.initfs.d/45_usbscsid.service | file missing | +| base | P2-init-acpid-wiring | init.initfs.d/41_acpid.service | file missing | +| kernel | P0-canary | src/arch/x86_shared/start.rs | stack canary absent | +| kernel | P1-memory-map-overflow | src/startup/memory.rs | overflow guard absent | +| kernel | P2-redbear-os-branding | 3 start.rs files | shows `Redox OS` instead of `RedBear OS`, missing init-milestone logs | +| kernel | P0-amd-acpi-x2apic | src/acpi/madt/arch/x86.rs | AMD x2APIC missing | +| relibc | P3-bits-eventfd-mod | src/header/mod.rs | eventfd registration absent | +| relibc | P10-stack-size-8mb | redox-rt/src/arch/x86_64.rs | main-thread stack still default (1 MB) | +| relibc | P0-relibc-syscall-0.8.1 | Cargo.toml | dependency on old 0.8.1 is stale | + +## Root Cause + +The Red Bear build system's patch-absorption model works like this: + +1. `local/patches//.patch` files are authored when a Red Bear + feature or fix is needed. +2. A developer is expected to apply the patch, test, **commit the change + to the local fork** (as a proper commit), and leave the patch file in + `local/patches//` as a recovery artifact. +3. AGENTS.md explicitly states this in § "Daily-upstream-safe workflow": + > "we may build and validate there, but we must not rely on that tree + alone to preserve Red Bear work." + +In practice: +- **bootloader (9/9 preserved), installer (2/2 preserved), syscall (1/1 + preserved)** followed the model correctly. +- **base, kernel, relibc** used a single mega-commit pattern + (`base: apply Red Bear patches on latest upstream/main`, + `Red Bear: migrate patches into local fork`) that **squashed many + patches into one commit**. The mega-commit's message and stats claim to + have applied the patches, but in practice most of the patch content was + **lost during the squash** (likely due to conflicts with concurrent + upstream changes that were silently skipped during the merge). + +## Why This Wasn't Caught Earlier + +1. **`verify-fork-versions.sh`** checks Cargo.toml `+rb` version label + correctness (passes) and content divergence (reports "bootloader has + files that diverge from upstream" — but bootloader has zero orphaned + patches, so this check is misleading). +2. **`verify-fork-functions.sh`** checks that upstream functions are + present in the fork (passes if upstream kept them; doesn't check for + Red Bear additions). +3. **`verify-absorbed-patches.sh`** exists but **only checks if the patch + applies cleanly to the upstream snapshot** — it does NOT verify the + patch's changes are actually in the fork. +4. **`check-unwired-patches.sh`** exists but checks only that patches + are wired into a recipe.toml (which all forks that use upstream + snapshots do via the `path = "..."` source pattern). It doesn't + check that the patches are committed. + +None of the build system checks ask: "For each patch file in +`local/patches//`, is the change actually committed to the fork +HEAD?" + +## Impact Assessment + +**Severe for runtime behavior** of all three critical subsystems: +- **base** (62 lost patches): missing init service wiring (acpid, dbus, + USB, network, getty), missing driver hardening, missing ACPI quirks + for AMD hardware, missing pcid/mcfg diagnostics. +- **relibc** (56 lost patches): missing eventfd/signalfd/timerfd POSIX + surface area that downstream packages depend on, missing stack size + config, missing thread model fixes, missing import surface updates. +- **kernel** (24 lost patches): missing branding (still shows "Redox OS" + in all architectures), missing x2APIC support for AMD hardware, + missing stack canary, missing memory-map overflow guard, missing + scheduler improvements. + +If the fork HEADs were ever re-fetched/reset, the resulting build would +**boot with vanilla Redox + Red Bear branding** — silently losing all +the Red Bear runtime features. + +## Recommended Action Plan (Phase 1 — STOP THE BLEEDING) + +### Phase 1.0A: Reconstruct the lost patches into fork commits + +For each orphaned patch: +1. Create a branch `redbear/0.3.1-absorb--` per patch. +2. Apply the patch content to the fork's working tree. +3. Verify the change builds (where feasible). +4. Commit with `git -C commit -m "absorb: from local/patches/"`. +5. Continue with the cascade `git -C push` to the canonical + `submodule/` branch in `RedBear-OS`. + +### Phase 1.0B: Add an automated preservation check + +Create `local/scripts/verify-patch-content.sh` that runs the audit +script and integrates with `build-preflight.sh` so the build refuses +to proceed if orphaned patches exist. + +### Phase 1.0C: Update verify-patches-set tooling + +`verify-absorbed-patches.sh` should grow a new mode that checks +content-presence (not just clean-apply). This is the missing check. + +### Phase 1.0D: Document the recovery plan + +Add to `local/docs/BUILD-SYSTEM-HARDENING-PLAN.md` an explicit rule: +"Each patch in local/patches// MUST correspond to either: + (a) a commit in local/sources// HEAD whose diff includes the + patch's added lines, OR + (b) a `path = "..."` recipe.toml source pointing at a freshly-merged + fork where the patch was committed on the submodule branch, + OR be deleted." + +## Status + +- 2026-07-12: Audit discovered. **NOT YET REMEDIATED.** + Phase 1.0A (reconstruction) is the immediate next action. + Phase 1.0B+ are follow-up improvements. diff --git a/local/scripts/build-preflight.sh b/local/scripts/build-preflight.sh index 12d685d9a8..e63d21d9bb 100755 --- a/local/scripts/build-preflight.sh +++ b/local/scripts/build-preflight.sh @@ -84,6 +84,22 @@ if [ -x "$SCRIPT_DIR/verify-fork-functions.sh" ] && [ "${REDBEAR_SKIP_FUNCTION_C fi fi +# Phase 1.0B: enforce patch-presence invariant. Every patch in +# local/patches// MUST have its content reflected as commits in +# local/sources//'s working tree, or the next fork reset will +# silently lose that work. See local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md. +# Default is warn-only; --strict was decided against because current state +# is mid-recovery and we don't want to block every operator build. +if [ -x "$SCRIPT_DIR/verify-patch-content.sh" ] && [ "${REDBEAR_SKIP_PATCH_CONTENT_CHECK:-0}" != "1" ]; then + if ! "$SCRIPT_DIR/verify-patch-content.sh" >/tmp/patch-content.out 2>&1; then + cat /tmp/patch-content.out >&2 + echo ">>> WARNING: Orphaned patches detected (Phase 1.0B). Build proceeding but Red Bear" >&2 + echo ">>> work in orphaned patches is NOT in the fork. See audit doc for recovery." >&2 + echo ">>> Set REDBEAR_SKIP_PATCH_CONTENT_CHECK=1 to suppress this warning." >&2 + echo ">>> Run ./local/scripts/verify-patch-content.sh --strict for exit-1 enforcement." >&2 + fi +fi + if [ -n "$RELEASE" ]; then bash "$SCRIPT_DIR/build-release-mode.sh" --release="$RELEASE" --config="$CONFIG" "${EXTRA_PACKAGES[@]/#/--extra-package=}" fi