ffaa4c6bb4
- 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
409 lines
19 KiB
Markdown
409 lines
19 KiB
Markdown
# 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.
|
|
|
|
## 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-<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/<comp>/ MUST correspond to either:
|
|
(a) a commit in local/sources/<comp>/ 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=<old-sha> 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/<fork> 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`.
|
|
|
|
4. **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`
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
```bash
|
|
# 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.sh` — **0 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
|
|
|