@@ -628,17 +628,13 @@ rr_write_record() {
628628# this script cannot prove.
629629
630630# Is HEAD the very merge this record was written for? ⚠️ Containment is NOT that
631- # question and using it for the rerun was the measured defect: every later commit
632- # on the branch contains the recorded pre-merge tip too, so a run made AFTER the
633- # operator finished step 3 by hand re-entered `rerun`, pinned the base back to the
634- # pre-merge shas, and took main's side over their own regeneration commit — which
635- # step 3 then COMMITTED, exit 0, nothing refused. Equality is the question, and
636- # both parents answer it: the recorded merge is the commit whose first parent is
637- # the recorded pre-merge tip and whose second is the main tip that was merged.
631+ # question, and asking it for the rerun was the measured defect: every later branch
632+ # commit contains the recorded pre-merge tip too, so a run made after the operator
633+ # finished step 3 BY HAND re-entered `rerun`, took main's side back over their own
634+ # regeneration commit, and step 3 COMMITTED it. Both parents answer equality.
638635rr_head_is_recorded_merge () {
639- rr_h1=" $( git rev-parse --verify --quiet ' HEAD^1' ) " || return 1
640- rr_h2=" $( git rev-parse --verify --quiet ' HEAD^2' ) " || return 1
641- [ " $rr_h1 " = " $rr_rec_branch_tip " ] && [ " $rr_h2 " = " $rr_rec_main_tip " ]
636+ [ " $( git rev-parse --verify --quiet ' HEAD^1' || true) " = " $rr_rec_branch_tip " ] &&
637+ [ " $( git rev-parse --verify --quiet ' HEAD^2' || true) " = " $rr_rec_main_tip " ]
642638}
643639
644640rr_classify () {
@@ -653,16 +649,14 @@ rr_classify() {
653649 fi
654650 if git merge-base --is-ancestor " $rr_rec_main_tip " HEAD 2> /dev/null; then
655651 # The recorded merge is IN this HEAD, so step 1 is behind us either way.
656- # ⛔ Redoing step 2 is sound ONLY while HEAD still IS that merge: a branch
657- # commit past it is work step 2 would discard. `handoff` means step 3's
658- # commit was refused and step 2 was left discharged in the index, so HEAD
659- # back AT the merge means the operator dropped that index and step 2 is
660- # owed again; HEAD past it means they committed and step 4 is what is owed .
652+ # ⛔ Redoing step 2 is sound ONLY while HEAD still IS that merge — a commit past
653+ # it is work step 2 would discard. `handoff` ( step 3 refused, the index left
654+ # discharged) AT the merge means that index was dropped and step 2 is owed again;
655+ # PAST it means the operator committed, so step 4 is owed. A `pending` record past
656+ # its merge says nobody ever marked step 2 done — refused below, ⛔ never redone .
661657 if [ " $rr_rec_phase " != done ] && rr_head_is_recorded_merge; then
662658 echo rerun
663659 elif [ " $rr_rec_phase " = pending ]; then
664- # Never marked, yet the branch moved past the merge: who discharged step 2
665- # is unreadable from here, so it is refused rather than redone.
666660 echo advanced
667661 elif [ " $( git rev-parse origin/main) " = " $rr_rec_main_tip " ]; then
668662 echo settled
@@ -697,28 +691,23 @@ rr_refuse_stale() {
697691 exit 1
698692}
699693
700- # The record still says `pending` and the branch has commits PAST the recorded
701- # merge — the state step 3's refusal used to leave behind, because it exits before
702- # marking anything and its own instruction finishes the commit OUTSIDE this script.
703- # Marked `handoff` that refusal no longer lands here; an unmarked record (a run
704- # killed after step 1, or one written before this refusal existed) still does.
694+ # Recorded `pending`, yet the branch has commits PAST the recorded merge — the state
695+ # step 3's refusal used to leave behind, since it exits before marking anything while
696+ # instructing the operator to finish the commit OUTSIDE this script. Marked `handoff`
697+ # that refusal no longer lands here; a record nothing ever marked still does.
705698rr_refuse_advanced () {
706699 echo " ✗ the recorded merge is in HEAD, the record still says \` pending\` , and this branch has" >&2
707700 echo " commits PAST that merge — refusing to redo step 2 over them." >&2
708701 rr_print_record
709- echo " \` pending\` means no run ever marked step 2 discharged; the commits past the merge mean" >&2
710- echo " somebody did something this script cannot read. Redoing step 2 now would pin the base to" >&2
711- echo " the PRE-MERGE shas above and take main's side of every generated path both sides moved —" >&2
712- echo " discarding those later commits' bytes — and step 3 would COMMIT that, exit 0." >&2
713- echo " ⛔ That is a revert wearing a repair's message." >&2
714- echo " If you finished step 3 BY HAND (its refusal says to), step 2 is already discharged and" >&2
715- echo " the sequence resumes at STEP 4 — drop the spent record and go there:" >&2
716- echo " rm -f $rr_record " >&2
717- echo " If it is NOT discharged, perform step 2 by hand against the RECORDED base first:" >&2
702+ echo " \` pending\` means no run ever marked step 2 discharged, and the later commits mean" >&2
703+ echo " somebody did. Redoing it now pins the base to the PRE-MERGE shas above, takes main's" >&2
704+ echo " side of every generated path both sides moved — discarding those commits' bytes — and" >&2
705+ echo " step 3 COMMITS that, exit 0: ⛔ a revert wearing a repair's message. Finished step 3 BY" >&2
706+ echo " HAND, as its refusal says to? Then step 2 IS discharged: \` rm -f $rr_record \` and resume" >&2
707+ echo " at STEP 4. If it is NOT, do step 2 by hand against the RECORDED base, commit, then go:" >&2
718708 echo " git diff --name-only $rr_rec_base $rr_rec_main_tip -- <the merge=os-regen patterns>" >&2
719709 echo " git diff --name-only $rr_rec_base $rr_rec_branch_tip -- <the same patterns>" >&2
720- echo " git restore --source=$rr_rec_main_tip -- <every path in BOTH lists> # tree only, ⛔ never --staged" >&2
721- echo " then commit that as its own commit and continue with step 4." >&2
710+ echo " git restore --source=$rr_rec_main_tip -- <every path in BOTH lists> # tree only" >&2
722711 exit 1
723712}
724713
@@ -1182,23 +1171,20 @@ mode_run() {
11821171 if [ -n " $( git status --porcelain) " ]; then
11831172 git add -A
11841173 if ! git commit --no-edit -m " merge origin/main (os-regen artifacts taken from main; regeneration follows)" ; then
1185- # ⛔ MARKED before this exit, and that marking is the repair: step 2 is
1186- # discharged in the index at this instant, and the line below hands the
1187- # commit to the operator — the ONE path that finishes step 3 outside this
1188- # script. Left `pending`, the next run read containment, re-entered `rerun`
1189- # and took main's side back over whatever that hand-off committed. The
1190- # recorded conflict set rides along so a rerun after a discarded index
1191- # still keeps the resolutions it asked for.
1174+ # ⛔ MARKED before this exit, and the marking is the repair: step 2 is discharged in
1175+ # the index at this instant and the line below hands the commit to the operator — the
1176+ # ONE path that finishes step 3 outside this script. Left `pending`, the next run read
1177+ # containment, re-entered `rerun` and took main's side back over whatever that hand-off
1178+ # committed. The conflict set rides along, so a DROPPED index still keeps resolutions.
11921179 rr_write_record handoff " $merge_base " " $branch_tip " " $main_side " " $rr_rec_conflicted "
11931180 echo " ✗ step 3's commit was refused — the merge is staged but NOT committed." >&2
11941181 echo " ⛔ Do not regenerate yet: with a staged index a bare \` git commit\` after" >&2
11951182 echo " regeneration lands the STAGED side, not the tree you inspected." >&2
11961183 echo " Clear what the hook reported, then \` git add -A && git commit\` before step 4." >&2
1197- echo " Step 2 IS discharged in that index and the record now says so (phase=handoff):" >&2
1198- echo " $rr_record " >&2
1199- echo " ⛔ Do NOT rerun this script to finish that commit — it would redo step 2 against" >&2
1200- echo " the pre-merge base and take main's side back over what you just committed." >&2
1201- echo " Once the commit exists the sequence resumes at STEP 4." >&2
1184+ echo " Step 2 IS discharged in that index and the record says so (phase=handoff), so the" >&2
1185+ echo " sequence resumes at STEP 4 once that commit exists: $rr_record " >&2
1186+ echo " ⛔ Do NOT rerun this script to finish it — that redoes step 2 against the pre-merge" >&2
1187+ echo " base and takes main's side back over what you just committed." >&2
12021188 exit 1
12031189 fi
12041190 else
@@ -2161,6 +2147,47 @@ mode_self_test() {
21612147 " $( git show HEAD:gen/deferred.txt | grep -c ' deferred v1-MAIN' || true) " 0
21622148 cd " $here "
21632149
2150+ # --- 11. REPRO 1 (this card): step 3's commit REFUSED hands the commit to the
2151+ # operator, and until the record said so the NEXT run re-entered `rerun`, redid
2152+ # step 2 against the pre-merge base and COMMITTED a revert of the operator's own
2153+ # regeneration — exit 0, nothing refused. `FLOWX` stands in for it.
2154+ st_fixture_rerun " $tmp /m"
2155+ bash " $SELF " > /dev/null 2>&1 || true # run 1 stops on the MIXED conflict
2156+ st_resolve_and_commit && st_record=" $( rr_record_path) " # the merge, committed by hand
2157+ printf ' #!/bin/sh\nexit 1\n' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
2158+ out=" $( bash " $SELF " 2>&1 ) " && rc=0 || rc=$? # run 2: the rerun, step 3 refused
2159+ st_case ' r1: the refused step-3 commit fails, MARKS handoff, warns against rerunning' \
2160+ " $rc /$( rr_field phase " $st_record " ) /$( printf ' %s' " $out " | grep -c ' Do NOT rerun this script' || true) " ' 1/handoff/1'
2161+ # Then do EXACTLY what it says: clear the hook, regenerate, `git add -A && commit`.
2162+ rm -f .git/hooks/pre-commit && printf ' deferred v1-MAIN\nFLOWX\n' > gen/deferred.txt
2163+ git add -A && git commit -qm ' step 4 by hand: regenerate (FLOWX)'
2164+ cp -a " $tmp /m" " $tmp /m-mut" # 11b replays run 3 from right here
2165+ out=" $( bash " $SELF " 2>&1 ) " && rc=0 || rc=$? # run 3: the card's own run
2166+ st_case ' r1: run 3 exits 0 discharged — no rerun, no side taken, and FLOWX SURVIVES (the card read 0)' \
2167+ " $rc /$( printf ' %s' " $out " | grep -c ' ^→ RERUN:' || true) /$( printf ' %s' " $out " | grep -c ' already discharged' || true) /$( printf ' %s' " $out " | grep -c " TAKING main.s side" || true) /$( git show HEAD:gen/deferred.txt | grep -c FLOWX || true) " ' 0/0/1/0/1'
2168+ # The safety net for a record nothing ever marked (a run killed after step 1, or one
2169+ # written before the marking existed): refused, ⛔ never silently redone.
2170+ sed ' s/^phase=.*/phase=pending/' " $st_record " > " $st_record .t" && mv " $st_record .t" " $st_record "
2171+ out=" $( bash " $SELF " 2>&1 ) " && rc=0 || rc=$?
2172+ st_case ' r1: a pending record past its own merge is REFUSED with the by-hand step 2' \
2173+ " $rc /$( printf ' %s' " $out " | grep -c " git restore --source=$( rr_field main_tip " $st_record " ) " || true) " ' 1/1'
2174+ cd " $here "
2175+
2176+ # --- 11b. THE DISCRIMINATING MUTATION for case 11: put the containment gate back —
2177+ # the ONE line — and the card reproduces on demand, in the tree case 11 copied
2178+ # aside one commit earlier. Same perl/\Q..\E replacement 6b, 8b, 9b, 10b use.
2179+ mutated_reentry=" $tmp /mutated-reentry-os-regen-merge.sh"
2180+ MUT_ANCHOR=' if [ "$rr_rec_phase" != done ] && rr_head_is_recorded_merge; then' \
2181+ MUT_INSERT=' if [ "$rr_rec_phase" != done ]; then' \
2182+ perl -0777 -pe ' s/\Q$ENV{MUT_ANCHOR}\E/$ENV{MUT_INSERT}/' " $SELF " > " $mutated_reentry "
2183+ st_case ' the equality gate has ONE call site, the mutation took, and it parses' \
2184+ " $( sed -n ' 1,/^# --- self-test/p' " $SELF " | grep -c ' rr_head_is_recorded_merge; then' || true) /$( diff -q " $SELF " " $mutated_reentry " > /dev/null 2>&1 ; echo $? ) /$( bash -n " $mutated_reentry " > /dev/null 2>&1 ; echo $? ) " ' 1/1/0'
2185+ cd " $tmp /m-mut/work"
2186+ mut_out=" $( bash " $mutated_reentry " 2>&1 ) " && mut_rc=0 || mut_rc=$?
2187+ st_case ' mutated: run 3 re-enters the rerun, exits 0, takes main s side back, and step 3 COMMITS the revert — FLOWX gone' \
2188+ " $mut_rc /$( printf ' %s' " $mut_out " | grep -c ' ^→ RERUN:' || true) /$( printf ' %s' " $mut_out " | grep -c ' TAKING main.s side of gen/deferred.txt' || true) /$( git show HEAD:gen/deferred.txt | grep -c FLOWX || true) " ' 0/1/1/0'
2189+ cd " $here "
2190+
21642191 if [ " $st_fail " -ne 0 ]; then
21652192 printf ' ✗ os-regen-merge self-test: %d case(s) failed.\n' " $st_fail "
21662193 return 1
0 commit comments