Tasks: Terminus / Merge-Coord Integrity (Epic #5001)

Mission: terminus-merge-integrity-01M380R6 Branch: fix/terminus-merge-integrity · Topology: coord Spec: spec.md · Plan: plan.md · Contract: contracts/terminus-reconciliation.md Grounding: research.md · ../../work/epic-5001-research/DEBRIEF.md

Make Epic #5001's invariant executable and closed-by-construction: no terminus command (merge / merge --resume / merge --abort / upgrade / agent issue-verdict / doctor coordination --fix) may exit 0 while the target tree diverges from the claimed WP set or while committed work is destroyed. Ten work packages build one shared terminus transaction seam — CAS ref-advance → full post-checkpoint projection → fail-closed reachability verifier → gated teardown — backed by four single-authority consolidations (target, lock, residue-topology, lane-identity) and a surface-authority write gate.

Every WP is ATDD red-first: the failing reproduction lands (or is confirmed red) BEFORE the fix (DIRECTIVE_041/034, FR-014, NFR-001). Completion is event-sourced via spec-kitty agent tasks move-task <WPID> --to <lane> — no checkboxes, no frontmatter lane.

Subtask Index (reference only — not a tracking surface)

IDDescriptionWPParallel
T001tests/terminus/ harness: real-CLI-entry fixtures, coord mission builder, lane-tip approved-SHA helper, planted-canceled-commit helper (conftest.py)WP01[P]
T002Tier-0 property test: exit-0 terminus ⇒ every approved WP commit reachable, no excluded commit reachableWP01[P]
T003Per-child red repros — reconciliation/CAS family (#4945 #4977 #4981 #4991 #4996)WP01[P]
T004Per-child red repros — surface/resume/behind-HEAD family (#4969 #4970 #4973 #4978 #4982 #4985 #4997)WP01[P]
T005Confirm RED on base; non-vacuous excluded-check (planted canceled commit fires)WP01[P]
T006Red unit: advance_branch_ref argv is 3-arg CAS, fails closed on mismatchWP02[P]
T007Fix: advance_branch_ref 2-arg → 3-arg CAS (update-ref ref new old), fail closedWP02[P]
T008Fix: correct the module + advance_branch_ref docstring (drop lock-dependence claim)WP02[P]
T009Verify red→green + ruff/mypy gatesWP02[P]
T010Red: unmaterialized-coord terminus WRITE refuses (does not degrade to primary)WP03[P]
T011Fix: fail-closed WRITE gate at resolve_write_target_or_degrade (real degrade point)WP03[P]
T012Fix: surface_resolver.resolve_for_write thin helper the write chain callsWP03[P]
T013Fix: wire write chain — write_seam.write_artifact, issue_matrix, issue_verdictWP03[P]
T014Fix: implement._validate_base_ref consults origin/<lane> (#4969)WP03[P]
T015Verify red→green + ruff/mypy gatesWP03[P]
T016Red: lane id stable across WP-removal re-finalize; base consults origin/<lane>WP04[P]
T017Fix: mint stable lane_id once at creation (compute.py)WP04[P]
T018Fix: read back the bound id on finalize — never re-letter over a bound idWP04[P]
T019Fix: workspace/context.py origin-aware base resolutionWP04[P]
T020Verify red→green + ruff/mypy gatesWP04[P]
T021Red: behind-own-HEAD checkout distinguished from real local changesWP05[P]
T022Fix: preflight.py behind-HEAD classifier (behind-own-HEAD ≠ dirty)WP05[P]
T023Fix: never advise the merge-reverting "Commit" remedy (#4982/#4997)WP05[P]
T024Verify red→green + ruff/mypy gatesWP05[P]
T025Red: MergeOutcomeVerifier.verify reachability + fail-closed claim (test_reconciliation.py)WP06
T026Scaffold: all new _MergeRunState fields + linear phase slot + reconciliation.py stub (ONE change)WP06
T027Verifier claim integrity: fail-closed + Lamport-sourced membership + tree-tip approved SHAsWP06
T028Verifier reachability: excluded-by-patch-id; git_probes ancestry→tree-equalityWP06
T029Wire gate into executor between _phase_commit_and_assert and cleanup; inline manifest reseed-from-stateWP06
T030FR-012 legacy refuse + NFR-005 non-vacuous call-site gate over all 6 terminus entry points + self-mutation testWP06
T031Verify red→green (property + repros) + ruff/mypy gatesWP06
T032Red: post-checkpoint concurrent coord commit survives; changed coord tip aborts teardown (test_projection_teardown.py)WP07
T033Fix: project ALL post-checkpoint coord commits onto target (not just status files)WP07
T034Fix: teardown gated on reachability + coord-ref CAS (teardown.py)WP07
T035Verify red→green + ruff/mypy gatesWP07
T036Red: strand heal reverts only recorded SHAs; residue classified by stored topology (test_coherence_integrity.py)WP08
T037Fix: SHA-scoped strand heal (revert only recorded SHAs, never a range) (#4973)WP08
T038Fix: is_coord_residue_churn threads stored mission topology (#4978)WP08
T039Verify red→green + ruff/mypy gatesWP08
T040Red: --target > persisted > meta precedence across crash; abort frees only own lock (test_merge_state_authority.py)WP09
T041Fix: single persisted merge target (core/paths.py + resolve.py + state.py + merge.py)WP09
T042Fix: owner_token = merge-state-id; abort frees only own lockWP09
T043Verify red→green + ruff/mypy gatesWP09
T044Correct CLAUDE.md false CAS-advance + sole-authority-reducer claimsWP10
T045Correct ADR 2026-02-09-3 + status-model.md; keep #4990 named-openWP10
T046Final green: full blast-radius suite per quickstart.md (12 repros + property + gates)WP10

Work Packages

WP01 — Red-first terminus test harness (FR-014, NFR-001)

each driving the real CLI entry point (no _run_git/subprocess mocking), approved SHAs from lane-branch git tips, non-vacuous excluded-check (planted canceled commit).

excluded-check that no-ops when the canceled set is empty.

  • Goal: land the Tier-0 property test + 12 per-child reproductions RED against pre-fix behavior,
  • Owns: tests/terminus/. Profile: debugger-debbie. Deps**: none (parallel fan head).
  • Subtasks: T001–T005. Prompt: tasks/WP01-red-first-terminus-harness.md (~330 lines)
  • Risks: a repro that mocks _run_git is vacuous (does not exercise ref_advance CAS); an

WP02 — Compare-and-swap ref advance (S-A, FR-003)

drop the docstring's lock-dependence admission. Traces #4996 #4982 #4997.

  • Goal: advance_branch_ref uses 3-arg CAS update-ref ref new old, fails closed on mismatch;
  • Owns: src/specify_cli/git/ref_advance.py. Profile: python-pedro. Deps: none.
  • Subtasks: T006–T009. Prompt: tasks/WP02-cas-ref-advance.md (~250 lines)
  • Risks: a silent 2-arg fallback on CAS failure reopens #4996; retry-on-mismatch masks the race.

WP03 — Surface-authority WRITE gate (S-C, FR-006)

worktree/branch is unresolved/unmaterialized; implement._validate_base_ref consults origin/<lane>. Traces #4970 #4969.

tasks/issue_matrix.py, cli/commands/agent/issue_verdict.py, cli/commands/implement.py, coordination/surface_resolver.py. Profile: python-pedro. Deps: none.

write chain never calls (redundant guard, RN-F1).

  • Goal: a terminus WRITE fails closed at the real degrade point when the authoritative coord
  • Owns: coordination/write_seam.py, mission_runtime/write_target_degrade.py,
  • Subtasks: T010–T015. Prompt: tasks/WP03-surface-write-gate.md (~340 lines)
  • Risks: gating the READ path (breaks correct loud-primary-fallback); gating a helper the real

WP04 — Stable, origin-aware lane identity (C-4, FR-010)

a bound id); base resolution consults origin/<lane>. Traces #4945 #4969.

Profile: python-pedro. Deps: none.

  • Goal: mint a stable lane_id once at creation; read it back on finalize (never re-letter over
  • Owns: src/specify_cli/lanes/compute.py, src/specify_cli/workspace/context.py.
  • Subtasks: T016–T020. Prompt: tasks/WP04-stable-lane-identity.md (~280 lines)
  • Risks: PP-F5 — confirm finalize READS BACK the minted id rather than also-minting positionally.

WP05 — Behind-HEAD recovery remedy (FR-011)

the merge-reverting "Commit". Traces #4982 #4997.

Profile: python-pedro. Deps: none.

  • Goal: distinguish a checkout merely behind its own HEAD from real local changes; never advise
  • Owns: src/specify_cli/merge/preflight.py (reads merge/git_probes.py, does not edit it).
  • Subtasks: T021–T024. Prompt: tasks/WP05-behind-head-remedy.md (~250 lines)
  • Risks: a remedy that stages the lane's own already-merged files reverts the merge.

WP06 — Reconciliation gate scaffold + verifier (S-D, FR-001/FR-002/FR-012)

change) then the fail-closed verifier: reachability (approved SHAs from tree tips) + excluded-by- patch-id + Lamport-sourced claim, wired between _phase_commit_and_assert and cleanup, with the manifest reseed-from-state inlined; FR-012 legacy refuse; NFR-005 non-vacuous call-site gate over all 6 terminus entry points.

src/specify_cli/merge/git_probes.py. Profile: python-pedro. Deps: WP01, WP02.

vacuously (PP-F3); membership through LWW reduce_parsed (#4990) green-washes a rejection; a 7th unrouted terminus path bypasses the gate.

  • Goal: SCAFFOLD (all new _MergeRunState fields + phase slot + reconciliation.py stub in ONE
  • Owns: src/specify_cli/merge/executor.py, src/specify_cli/merge/reconciliation.py (NEW),
  • Subtasks: T025–T031. Prompt: tasks/WP06-reconciliation-gate-scaffold.md (~460 lines)
  • Risks: a claim sourced through the READ resolver degrades to empty primary → gate passes

WP07 — Projection + gated teardown (S-B, FR-004)

gate teardown on reachability + coord-ref CAS. Traces #4981 #4970 #4973. Change callee internals only — the executor call sites are already wired by WP06.

Profile: python-pedro. Deps: WP06.

verdict/emit commits; teardown that proceeds when the coord tip moved since checkpoint.

  • Goal: project ALL post-checkpoint coord commits onto the target (not only status files);
  • Owns: src/specify_cli/merge/bookkeeping_projection.py, src/specify_cli/coordination/teardown.py.
  • Subtasks: T032–T035. Prompt: tasks/WP07-projection-gated-teardown.md (~280 lines)
  • Risks: projecting only status.events.jsonl/status.json (today's bug) still drops concurrent

WP08 — Coherence integrity: SHA-scoped heal + topology residue (S-B heal + C-3, FR-005/FR-009)

party's later event); is_coord_residue_churn threads the stored mission topology. Traces #4973 #4978.

topology-blind classifier reset --hards lanes/single_branch planning artifacts.

  • Goal: strand heal reverts only explicitly recorded SHAs (never a range revert erasing a third
  • Owns: src/specify_cli/coordination/coherence.py. Profile: python-pedro. Deps: WP07.
  • Subtasks: T036–T039. Prompt: tasks/WP08-coherence-integrity.md (~270 lines)
  • Risks: a git revert captured..HEAD range under a shape-only guard erases a reopen event; a

WP09 — MergeState authority: single target + owned lock (C-1/C-2, FR-007/FR-008)

the sole authority for all phases and --resume; owner_token = merge-state-id; --abort frees only its own lock. Traces #4985 #4991 #4996.

src/specify_cli/merge/state.py, src/specify_cli/cli/commands/merge.py. Profile: python-pedro. Deps: WP08.

(serial after WP06, no collision) — record the rationale in the WP + PR.

  • Goal: resolve+persist the landing target once (precedence --target > persisted > meta) as
  • Owns: src/specify_cli/core/paths.py, src/specify_cli/merge/resolve.py,
  • Subtasks: T040–T043. Prompt: tasks/WP09-mergestate-authority.md (~300 lines)
  • Note: one documented out-of-map edit to executor.py's lock-acquire call site is permitted
  • Risks: owner_token = pid cannot survive the crash the lock protects (--resume = new pid, PP-F2).

WP10 — Doc/doctrine correction + final green (FR-013)

reducer" claims, the ADR reconciliation note, and status-model.md; keep #4990 named-open; run the full blast-radius suite green.

docs/architecture/status-model.md. Profile: curator-carla. Deps: WP03, WP04, WP05, WP09.

  • Goal: correct CLAUDE.md's false compare-and-swap-advance + "sole-authority deterministic
  • Owns: CLAUDE.md, docs/adr/2.x/2026-02-09-3-event-log-merge-semantics.md,
  • Subtasks: T044–T046. Prompt: tasks/WP10-doc-doctrine-final-green.md (~230 lines)
  • Risks: over-claiming verdict-integrity a green Tier-0 does not provide while #4990 is open.

Parallelization

Parallel fan (file-isolated, start immediately):
  WP01  tests/terminus/**                      (red-first harness)
  WP02  git/ref_advance.py                      (S-A CAS)
  WP03  write_seam/write_target_degrade/…       (S-C write gate)
  WP04  lanes/compute.py + workspace/context.py (C-4 lane identity)
  WP05  merge/preflight.py                       (FR-011 behind-HEAD)

Serialized executor lane (one owner; _MergeRunState kept coherent):
  WP06  scaffold + verifier + gate wiring   ← WP01, WP02
  WP07  projection + gated teardown         ← WP06
  WP08  coherence heal + residue topology   ← WP07
  WP09  single target + owned lock          ← WP08

Integration:
  WP10  docs + final green                  ← WP03, WP04, WP05, WP09

The only multi-writer files — merge/executor.py and coordination/coherence.py — are confined to the single serial lane; no parallel-fan WP touches them. WP02 (CAS) is in the fan but MUST land before WP06 starts (S-D pairs with atomic advance). WP01's Tier-0 property test is the shared gate: red before, green after the serial lane + companions land.

MVP scope

WP06 (S-D reconciliation gate) + WP02 (S-A CAS) are the MVP: together they make the epic invariant executable and atomic, converting ~9–10 of the 12 defects from "exit 0 + destroyed work" into honest fail-closed outcomes (US1). Everything else is defence-in-depth around them.

Close-out checklist (not a WP — tracked here so it is not honor-system)

Per the mission brief, after spec-kitty merge consolidates lanes into local main:

__init__.py bump).

#4997 in-mission → fixed via spec-kitty agent issue-verdict before move-task --to approved. (#4990 and #4972 stay named-open as spun-out siblings.)

  • □ CHANGELOG.md entry for the Epic #5001 terminus-integrity fix (owed even without an
  • □ Set issue-matrix verdicts #4945 #4969 #4970 #4973 #4977 #4978 #4981 #4982 #4985 #4991 #4996
  • □ Pre-merge review squad over the final aggregate diff; fold all findings.
  • □ Clean branch history (coherent commits, #-referenced, attribution footer).
  • □ Open the draft PR targeting main; the operator merges.