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.
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.