Tasks: Merge resume primary-site staged-deletion integrity
Mission: resume-primary-staged-deletion-integrity-01M3CCH9 · Issue: #4997 (P0, milestone #11) Spec: spec.md · Plan: plan.md
Two work packages, one lane (both touch merge/executor.py → collapse by write-scope; WP02 depends on WP01). Red-first (ADR 2026-07-17-1): each defect lands an issue-pinned @pytest.mark.regression repro that is RED through the real spec-kitty merge entry point before the fix; transitional repros become focused units after the fix.
Work Package WP01: Phantom-only resume recovery + faithful repro (Defect A, P0)
Dependencies: None Requirement refs: FR-001, FR-002, FR-004, NFR-001, NFR-002, C-001 Authoritative surface: src/specify_cli/merge/
model the REAL behind-own-HEAD window: advance the target so the mission branch is an ancestor of the primary HEAD; persist pre_mutation_target_sha = pre-advance target tip; leave the primary tree/index at that base with phantom staged deletions of the mission's OWN files. Keep the test name, assertion, and regression-shape. Confirm it is RED (still xfail/failing) BEFORE the product fix.
merge/preflight.py): True iff HEAD is a descendant of base_sha AND the primary's working tree AND index are byte-identical to base_sha (git diff --quiet <base> and git diff --cached --quiet <base> both clean). Focused unit tests for each branch.
on MERGE_UNSAFE_PRIMARY_DIRTY + a resume + classify_resume_dirty_remedy == BEHIND_OWN_HEAD + is_pure_behind_head_lag(..., base_sha=state.pre_mutation_target_sha): run git -C main_repo reset --hard HEAD, re-run the pre-mutation preflight once, and continue into the locked flow. Otherwise abort unchanged (advisory). Keep complexity ≤15 (extract a helper).
window where the primary ALSO carries a genuine tracked edit (tree differs from pre_mutation_target_sha) → merge --resume REFUSES (rc != 0) and the genuine edit survives (no reset --hard). Guards Renata's data-loss hole.
test_repro_4982.py still PASSES (no regression).
- T001 — Correct
tests/terminus/test_repro_4997.py::_interrupt_with_staged_deletionto - T002 — Add
is_pure_behind_head_lag(repo_root, *, base_sha, env)(in - T003 — At
merge/executor.pyexcept DestructiveOpRefusedhandler (the resume path), - T004 — New safety test
tests/terminus/test_resume_phantom_only.py: a behind-own-HEAD - T005 — Remove the
xfailfromtest_repro_4997.py; prove it PASSES. Prove
Work Package WP02: Merge-strategy no-op adjudication (Defect B)
Dependencies: WP01 Requirement refs: FR-003, NFR-003, C-002 Authoritative surface: src/specify_cli/lanes/
strategy, mission branch already an ancestor of the target but the target tree diverges from the mission tree (mission content removed on top) → merge --resume --strategy merge must REFUSE (rc != 0), NOT stamp WPs done / delete lanes / exit 0. Confirm RED first.
date" no-op (capture the target tip before git merge; if unchanged after, it is a no-op) and return False when allow_noop_squash (resume tolerance) is set, else raise — mirroring the squash zero-staged path. So already_applied=True propagates for the merge no-op.
(_reject_zero_diff_noop_integration) in merge/executor.py (honest naming; the guard is already strategy-agnostic). Update its one call site + any test references.
blind spot) in the PR body, not a ticket.
- T006 — New red-first test
tests/terminus/test_merge_noop_adjudication.py: merge - T007 — In
lanes/merge.py::_merge_branch_intoMERGE branch: detect the "Already up to - T008 — Rename
_reject_zero_diff_noop_squash→ strategy-neutral - T009 — Prove T006 now PASSES; record the C-002 residual (merge-axis reconciliation