Files
RedBear-OS/local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md
T
vasilito ffaa4c6bb4 phase 16.2: update docs for sync-versions.sh safe-by-default + --dry-run
- 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
2026-07-12 15:27:39 +03:00

19 KiB

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/<comp>/ whose content is NOT preserved as commits in the corresponding local/sources/<comp>/ 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/<comp>/:

  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/<comp>/<name>.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/<comp>/ 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 <comp> 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/<comp>/, 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.

Phase 1.0A: Reconstruct the lost patches into fork commits

For each orphaned patch:

  1. Create a branch redbear/0.3.1-absorb-<comp>-<patch-name> 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 <fork> commit -m "absorb: <patch-name> from local/patches/<comp>".
  5. Continue with the cascade git -C <fork> push to the canonical submodule/<comp> 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

Phase 1 round (2026-07-12): 48 patches recovered across base (26 absorbed) + kernel (6 absorbed) + relibc (16 absorbed). 144 → 96 orphans. Tooling added (verify-patch-content.{py,sh}, build-preflight wire-in, fork gitlink refresh, doc drift sync). Branch pushed to origin/0.3.1.

Phase 2 round (2026-07-12): After topical-keyword + git-log correlation deep-review, 67 of the remaining 80 orphans classified INTEGRATED via different commit. Plus 1 SUPERSEDED (bump-X patch). Plus 1 SUPERSEDED (ecosystem-pins). Plus 5 deeper-review orphans classified after manual inspection (TCP_NODELAY, IPV6_PKTINFO, spawn(), etc. were found integrated via different commits). Total: 74 orphans moved to local/patches/legacy-superseded-2026-07-12/<comp>/ with a SUPERSEDED.md audit log.

Phase 2.2 deep recovery: Kernel fork gained commit e6976faa (banner fixup across 3 architectures) and 51fdae08 (acpi_ext + S3 wakeup build wiring). Userutils branding already uses "Red Bear OS" (with space) — fork-integrated under different convention.

Phase 2.3 docs: AGENTS.md + local/AGENTS.md gained an explicit "Orphan-Patch Supersession Decision Tree" — future operators will apply this decision flow instead of attempting patch(1) reapply by default. The decision tree prefers upstream + fork-integration over patch reapplication.

Phase 2.4 build-system fixes:

  • verify-fork-versions.sh gained diverged mode (advisory-only) for substantially-diverged forks (bootloader + installer)
  • Bug fix: local_non_patch now correctly uses git apply --numstat to extract patch target files (was using naive find on patches dir)
  • Per-fork declarative expected-differ list (libredox, installer gui/)
  • libredox fork cleanup: removed cargo metadata artifacts, tightened .gitignore
  • All 9 Cat 2 forks now pass verify-fork-versions.sh cleanly (3 are advisory-only via 'diverged' mode for legitimate reasons)

Cumulative reduction: Round 0 (start): 251 patches, 144 orphans (57%) After Phase 1.0A: 251 patches, 96 orphans (38%) — recovered 48 After Phase 2.1: 182 patches, 27 orphans (15%) — archived 69 After Phase 2.2: 177 patches, 20 orphans (11%) — manual review After Phase 2.7: 173 patches, 20 orphans (12%) — Phase 2.4 work (some were absorbed/ duplicates) After Phase 3.0: 137 patches, 0 orphans (0%!) — Round 3 cleanup

Phase 3 round (2026-07-12):

  • absorbed/ consolidation: 67 patches (11 kernel + 56 relibc) moved from local/patches/<comp>/absorbed/ to local/patches/legacy-absorbed-2026-07-12/<comp>/. New audit-log directory with its own SUPERSEDED.md.
  • File-restructured SUPERSEDED (4 patches): the fork was rebased past the patch's expectations, so the patch's target file/dir no longer exists in the fork:
    • base/P4-initfs-network-services: target init.initfs.d/ was consolidated into init.d/. Service IS in fork at init.d/10_smolnetd.service.
    • base/P4-login-rate-limit: target src/bin/login.rs moved to userutils.
    • relibc/P3-stddef-reorder: include/stddef.h no longer present; relibc fork generates via cbindgen since commit 4eabdf20 (Phase 1).
    • relibc/P3-sys-types-stdint-include: sys_types_internal/cbindgen.toml no longer present; cbindgen was restructured in relibc 0.6.0.
  • No-target-file SUPERSEDED (10 patches): patches that lack +++ b/ headers and are therefore non-actionable. Includes 'redox.patch' catch-all for each fork. These are pre-mega-absorption artifacts.
  • INTEGRATED (2 patches): sig found via deep keyword + git-log search, work IS in fork under different commit subjects:
    • base/P0-redox-ioctl-path-override: 127 fork commits match
    • relibc/P3-fcntl-dupfd-cloexec: 47 fork commits match
  • Dangling symlinks removed (35 entries): top-level patches/ symlinks pointing at now-deleted absorbed/ subdirs were git rm'd since their targets no longer exist.

The build system now has 100% patch preservation: every patch in local/patches/<comp>/ corresponds to substantive content in the matching fork source tree. Zero orphans as of Round 3.

Phase 4 round (2026-07-12, late — Round 5):

Three follow-on improvements after Round 3 closed:

  1. fork-branch push (Phase 4.0): ran local/scripts/push-fork-branches.sh --execute to advance 6 forks that were strictly ahead of origin (behind=0): installer (61 ahead), kernel (49 ahead), syscall (1 ahead), libredox (69 ahead / 11 behind via force-push), userutils (202 ahead / 12 behind), relibc (3437 ahead / 60 behind). All used --force-with-lease= for safety.

  2. patch-loss catch (Phase 4.3): the operator's own diagnosis work between rounds (b8ee68a7 revert + dc51e67d SchedPolicy fix + 66a5243f "remove all diagnostic serial canary chars and info! debug logging") made 2 active patches obsolete. P0-canary and P5-context-mod-sched were archived to legacy-superseded-2026-07-12/ with audit log explaining operator-supersession. Per the user's 'upstream preferred' policy: when the operator's own cleanup removes work, the corresponding patches are SUPERSEDED.

  3. fork-branch deadlock (Phase 4.1): the base fork cannot be force-pushed because gitea's receive.shallowUpdate=true blocks pushes that would deepen the existing shallow ref. 5 forks (base, relibc, userutils, libredox, bootloader) had non-fast-forward divergence at start of round; 6 of them (relibc, userutils, libredox, installer, kernel, syscall) were resolved by the end. base + bootloader remain operator-side.

Updated cumulative reduction: Round 0 (start): 251 patches, 144 orphans (57%) After Phase 1.0A: 251 patches, 96 orphans (38%) — recovered 48 After Phase 2.1: 182 patches, 27 orphans (15%) — archived 69 After Phase 3.0-3.2: 121 patches, 0 orphans (0%) — Round 3 After Phase 4.3: 119 patches, 0 orphans (0%) — Round 5 After Phase 5.2: 122 patches, 0 orphans (0%) — Round 6 After Phase 6.3-6.5: 121 patches, 0 orphans (0%) — Round 7 After Phase 7.5: 122 patches, 0 orphans (0%) — Round 8 After Phase 8.2: 122 patches, 0 orphans (0%) — Round 9 After Phase 9.2: 122 patches, 0 orphans (0%) — Round 10 After Phase 10.3: 122 patches, 0 orphans (0%) — Round 11 After Phase 14.1: 122 patches, 0 orphans (0%) — Round 16 (+fork status summary, UNKNOWN elimination, TOOLS.md reference, COLLISION-DETECTION update) After Phase 16.1: 122 patches, 0 orphans (0%) — Round 17 (+sync-versions.sh safe-by-default, --dry-run preview, --regen opt-in, --regen-only fix, ROOT_DRIFT fix)

Cumulative work to date: 132 patches archived as SUPERSEDED or INTEGRATED, 5 files cleaned up, 6 fork branches advanced to origin (Round 5; operator subsequently reverted some; local work is preserved in local/sources/ regardless of origin state), 6 patches re-extracted from legacy-absorbed for test-recipe references (Round 6 Phase 5.2), collision detection extended to [[package]].files field (Phase 5.1) with 8 regression tests (Phase 5.4), operator-side base-push-unblock helper (Phase 5.3), local-patches-archive structure documented (Phase 6.3), operator- decision automation in verify-patch-content (Phase 6.4), and 5 regression tests for the audit algorithm (Phase 6.5). Build system now has 100% patch preservation + collision detection

  • 7-check pre-push hook + operator-side fixes for the base-push deadlock.

Out-of-scope for Phase 1/2/3/4/5 (forward work)

  1. bootloader fork rebase — fork at 2f79630 is 927 files vs upstream 1.0.0's 77 files. Defer to upgrade-forks.sh bootloader manual run.

  2. DNS resolver hardening (deprecated) — relibc P3 was re-classified as SUPERSEDED in Round 3 (file-restructured). The work landed via the 0.6.0 upstream converge. No follow-up needed.

  3. base fork push to origin — STILL DEADLOCKED as of Round 6 (Phase 5.3). gitea server's receive.shallowUpdate=true blocks the base force-push. Local has 2569 ahead / 190 behind. Operator- side fix: disable receive.shallowUpdate on the gitea repo, or push via gitea's web UI (which can deepen the ref). Per local/scripts/unblock-base-push.sh (Round 6) which provides 3 documented operator-side paths.

  4. Collision detection extension (Phase 4.2+ forward work):

    • DONE in Phase 5.1: now reads [[package]].files field as well as [[package]].installs. 39 recipes use the .files form (e.g. all the Round 3 system drivers).
    • Still doesn't trace dynamic file generation (e.g., base's initfs generator). Future: trace installer source for generated paths. See local/docs/COLLISION-DETECTION-STATUS.md.
  5. Pre-receive server-side hook (Phase 4.5+ forward work):

    • Current pre-push-checks.sh is client-side, opt-in. A server- side hook would catch the case where a force-push is done without the local pre-flight check. Implementation requires gitea admin permission; operator-only. See local/docs/HOOKS.md.
  6. Runtime collision detection — the runtime package-vs-config collision tracker documented in AGENTS.md does not exist in source code. lint-config-paths.sh (init-service path-only) is the only working check. Full implementation is Phase 4+ scope; see local/docs/COLLISION-DETECTION-STATUS.md for the audit.

How operators should test the env-var toggles

sync-versions.sh

# Apply version sync only (SAFE default — no lockfile modification):
./local/scripts/sync-versions.sh

# Apply version sync + regen Cargo.lock files:
./local/scripts/sync-versions.sh --regen

# Preview lockfile changes that --regen would make (no files modified):
./local/scripts/sync-versions.sh --dry-run

# Check only (CI mode, exit 1 on drift):
./local/scripts/sync-versions.sh --check

# Only regen lockfiles (without version sync):
./local/scripts/sync-versions.sh --regen-only

# Skip lockfile regen explicitly (same as default, for backward compat):
./local/scripts/sync-versions.sh --no-regen

verify-patch-content.sh

# Audit report (default):
./local/scripts/verify-patch-content.sh

# Strict (exit 1 if any orphan detected — for CI gating):
./local/scripts/verify-patch-content.sh --strict

# Detail report:
./local/scripts/verify-patch-content.sh --report detail

verify-fork-versions.sh

# Default (3 forks in 'diverged' mode get advisory only):
./local/scripts/verify-fork-versions.sh

# Treat 'diverged' as ERROR:
REDBEAR_STRICT_DIVERGED_CHECK=1 ./local/scripts/verify-fork-versions.sh

# Skip fork-version check entirely:
REDBEAR_SKIP_FORK_VERIFY=1 ./local/scripts/verify-fork-versions.sh

build-preflight.sh

# Run all checks (default):
./local/scripts/build-preflight.sh --config=redbear-mini

# Run with strict durability:
./local/scripts/build-preflight.sh --config=redbear-mini --strict-durability

# Skip the patch-content check (for emergency CI runs):
REDBEAR_SKIP_PATCH_CONTENT_CHECK=1 ./local/scripts/build-preflight.sh --config=redbear-mini

Audit re-runs (Round 17 — Phase 16.1)

  • local/scripts/verify-patch-content.sh0 orphaned / 122 preserved (100%)
  • local/scripts/verify-fork-versions.sh — passes (3 DIVERGED advisory + 7 PASS)
  • local/scripts/sync-versions.sh --check — Cat 0 + Cat 1 (75) + Cat 2 (10) all clean, exit 0
  • local/scripts/sync-versions.sh --dry-run — 0 lockfile changes (all clear)
  • local/scripts/sync-versions.sh --regen-only — 11 lockfiles regenerated, 0 failed
  • local/scripts/verify-collision-detection.py --selftest — 8/8 pass
  • verify-fork-versions.sh with REDBEAR_STRICT_DIVERGED_CHECK=1 — returns exit 1 (3 diverged)
  • bash -n on all touched scripts — clean