Implementation Plan: Terminus Integrity Follow-ups
Branch: fix/terminus-integrity-followups | Date: 2026-09-24 | Spec: spec.md Input: Feature specification from kitty-specs/terminus-integrity-followups-01M393QR/spec.md
Summary
Close the last three open Epic #5001 workstreams left after PR #5012 landed the reconciliation-gate spine, by extending — not re-architecting — that spine:
- WS1 (#5013, P1): make the DEFAULT squash merge enforce the excluded/closed-world content guarantee via a squash-sound blob-attribution axis (attribute the squashed diff's content paths against approved lanes' first-parent authored blobs — content, never SHAs/patch-ids). Replaces the squash PASS early-return in
MergeOutcomeVerifier.verify. - WS2 (#4982 #4997 #4985 #4991, P2): make
merge --resumehonor the persisted strategy (todayMergeState.strategyis a dead field) and preserve pre-interrupt lane-tip commits (today the reconciliation claim's coordination base is a live resume-start checkpoint, asymmetric with the already-persisted target window base). - WS3 (#4970, P3): make the coordination write gate refuse a stale-local-head self-materialization that already carries committed matrix content, and route
issue-verdictwrites through the fail-closed write resolver.
Grounding: three read-only research lenses (work/epic-5001-research/followup-{architect,debbie,paula}.md), each re-verified against this base (fix/terminus-merge-integrity HEAD, the #5012 spine).
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: typer (CLI), rich (console), subprocess-driven git (no GitPython); pytest for tests Storage: git refs / trees / blobs (the authority the gate verifies); MergeState JSON at .kittify/merge-state.json; status.events.jsonl; issue-matrix under kitty-specs/<slug>/ Testing: pytest — the mock-free real-CLI tests/terminus/ harness (red-first repros), plus tests/merge/, tests/git/, tests/coordination/, tests/mission_runtime/ unit coverage Target Platform: cross-platform CLI (Linux / macOS / Windows 10+) Project Type: single (CLI library under src/) Performance Goals: CLI operations < 2s typical; new git probes add a bounded number of git invocations per merge (no O(commits²) scans) Constraints: fail-closed on any ambiguity/probe-error (NFR-001); no new mission_runtime → specify_cli first-level subpackage import edge and no _baselines.yaml cap growth (C-002); every new function ≤ McCabe 15 (NFR-004); no new suppressions Scale/Scope: repository-internal terminus/merge machinery in src/specify_cli/{merge,coordination,git} + src/mission_runtime/; ~6 production files, ~5 test files; no dependency changes (so the Supply-Chain planning section does not apply — no add/upgrade/remove of any package)
Constitution Check (Charter)
GATE: Must pass before Phase 0. Re-checked after Phase 1 design (below).
- ATDD-first (C-011): every WP lands its failing test first. The
tests/terminus/repros arexfail(strict)today; markers come off only on genuine green. ✅ planned. - Architectural gate discipline / non-vacuity (DIRECTIVE_043, SO#5): the fixes close defect classes fail-closed (REFUSE on ambiguity), with positive+negative test arms; no vacuous PASS. ✅ planned (NFR-001, NFR-003).
- Red-main / no green-washing (SO#9, SO#4): honest xfail residuals; the 3-way merge-resolution content residual stays a tracked follow-up (C-003). ✅.
- Canonical sources (SO#6): extend the existing verifier/state/resolver seams; no improvised parallel authority. ✅.
- Boundary integrity (C-002, layer rules): no new layer edge; WS3 helper homes inside the already-ledgered
coordinationsubpackage. ✅ (re-verify post-design). - Change-scope reconciliation: smallest-viable-diff picks the file set; boy-scout cleanup stays inside touched files; no scope creep beyond the three workstreams. ✅.
- Terminology canon: Mission, not Feature; no
--featureflags introduced. ✅. - Git/workflow (DIRECTIVE_045): PR-bound; operator merges; consolidated linear history at closeout. ✅.
No violations → Complexity Tracking empty.
Project Structure
Documentation (this mission)
kitty-specs/terminus-integrity-followups-01M393QR/
├── plan.md # This file
├── research.md # Phase 0 — consolidated squad design
├── data-model.md # Phase 1 — data-structure changes
├── quickstart.md # Phase 1 — how to validate each workstream
├── contracts/ # Phase 1 — behavioral invariant contracts
└── tasks.md # Phase 2 (/spec-kitty.tasks — NOT created here)
Source Code (repository root)
src/specify_cli/
├── merge/
│ ├── reconciliation.py # WS1: authored_blobs (FINAL blob per (lane,path)) + _unattributable_content_squash (diff --name-status A/M, REFUSE on "") + reorder None-base/empty-authored REFUSE above squash early-return
│ ├── git_probes.py # WS1: blob_id_at(); changed_paths_in_range(--no-renames); first_parent_commits_in_range propagates GitProbeError — all raise → REFUSE
│ ├── executor.py # WS1 honest pass-message (MUST NOT strip enforce_closed_world in the verify_reachability=False replace); WS2 strategy reseed + pre_mutation_coord persist (read-persisted-first) + resume anchoring (SINGLE OWNER)
│ └── state.py # WS2: additive fields only — strategy, pre_mutation_coord_sha/_ref, pre_interrupt_lane_tips
├── cli/commands/
│ ├── merge.py # WS2: resume strategy precedence (explicit > persisted > config > SQUASH); REFUSE on strategy-flip (H1)
│ └── agent/issue_verdict.py# WS3: do_issue_verdict routes through resolve_for_write
├── coordination/
│ └── surface_resolver.py # WS3: committed-matrix-content probe (placement-authority path / ls-tree subtree; path-drift ≠ absent) beside _coord_branch_is_local_head
└── ...
src/mission_runtime/
└── write_target_degrade.py # WS3: assert_coord_write_materialized refuses stale-local-head-with-committed-content
tests/
├── terminus/ # red-first repros (WP01 owns; markers removed at integration WP06 WITH captured before/after)
│ ├── conftest.py # blob_present_at helper + plant_canceled_commit returns the planted path
│ ├── test_repro_{4945,4977,4981}.py # + DEFAULT-squash variants (blob/tree-presence observable, pin FAIL cause)
│ ├── test_repro_{4970,4982,4985,4991,4997}.py # xfail → green
│ └── test_terminus_reconciliation_property.py # [squash] param xfail → green + new clean-squash param
├── merge/
│ ├── test_git_probes_seam.py # blob-probe unit tests (WP03, canonical seam home)
│ ├── test_reconciliation.py # squash content-axis unit tests incl. FINAL-blob / second-parent exclusion (WP03)
│ └── test_merge_state_*.py # strategy precedence + H1 flip; lane-tip CAS H3 (ancestor OK) + absent-base H4 (WP04)
├── coordination/ | mission_runtime/ # write-gate + issue-verdict-reroute unit tests, path-drift REFUSE (WP02)
Structure Decision: single-project CLI library. The load-bearing conflict-avoidance rule (learned from the #5012 mission retrospective): executor.py has exactly one owning WP (WP05). WS1's honest pass-message and WS2's strategy-reseed + resume-anchoring both live in executor.py. Per the post-plan architect finding, the strategy persist is homed as a WP05 executor reseed (mirroring the landed C-1 target reseed at executor.py:2735), so resolve.py is NOT touched and the WP04↔WP05 contract stays pure-additive MergeState fields — no required-kwarg change across WPs. WS1 needs no executor authored_blobs change: _reconciliation_claim_for_gate's replace(captured, verify_reachability=False) already preserves enforce_closed_world/authored_blobs (WP05 must not strip them). Every other production file is single-owned. tests/terminus/ is owned by WP01 (which ADDS red tests but never removes a marker); xfail-marker removal happens at integration as an orchestrator-at-consolidation step (NOT a WP — WP06 owns docs only; a WP owning the same repro files WP01 owns would be a static ownership overlap), serially, with captured per-repro before/after evidence, reviewed by the mandatory pre-merge squad. This is the established #5012 pattern.
Parallel Work Analysis
Dependency Graph
Parallel fan (no deps, disjoint files):
WP01 test-harness (tests/terminus) ─┐
WP02 WS3 surface-write ─┤
WP03 WS1 content-axis (git_probes+reconcil.) ─┤
WP04 WS2 resume state+cli ─┤
│
Serial integration (shared executor.py): │
WP05 executor integration (deps WP03, WP04) ──┘→ WP05
│
Integration + close: ▼
WP06 markers+docs+final-green (deps WP01..05) ───────→ WP06
Critical path: {WP03 | WP04} → WP05 → WP06 (depth 3)
Work Distribution
- Sequential work:
executor.pyedits are all in WP05 (depends on WP03's reconciliation API + WP04'sMergeStatefields). Integration/marker-removal/docs in WP06. - Parallel streams: WP01 (tests/terminus harness), WP02 (WS3 files), WP03 (git_probes+reconciliation), WP04 (state+resolve+cli merge.py) run concurrently — file-disjoint.
- Agent assignments (no file overlap is the real guard):
- WP01 → debugger-debbie —
tests/terminus/(harness + red-first repros incl. blob/tree-presence default-squash variants that pin the FAIL cause + the hard-coupled clean-squash positive) - WP02 → python-pedro —
write_target_degrade.py,surface_resolver.py,issue_verdict.py+tests/coordination|mission_runtime/unit tests (incl. path-drift REFUSE, first-write success, flat/no-coord no-false-refuse); must run + record the F11 blast-radius (tests/coordination+status+cli+issue_matrix) and choose file-existence vs row-level accordingly - WP03 → python-pedro —
merge/git_probes.py,merge/reconciliation.py+tests/merge/test_git_probes_seam.py,tests/merge/test_reconciliation.py(incl. FINAL-blob / second-parent-exclusion + per-REFUSE-branch error-injection) - WP04 → python-pedro —
merge/state.py(additive fields),cli/commands/merge.py(strategy precedence + H1 flip-REFUSE) +tests/merge/test_merge_state_*.py(precedence table, H1, lane-tip CAS H3 ancestor-OK, absent-base H4) - WP05 → python-pedro —
merge/executor.pyonly (honest pass-message without strippingenforce_closed_world; strategy reseed = persist attempt-1's executed value;_resolve_pre_mutation_coord_sharead-persisted-first; per-lane tip persist + resume anchoring at the consumption site) - WP06 → curator-carla / implementer-ivan (NOT the reviewer) — marker removal WITH captured before/after per repro, CHANGELOG + docs, final aggregate green
Coordination Points
- Integration: WP05 consumes WP03's
reconciliationAPI (authored_blobs + squash axis) and WP04'sMergeStatefields; both are additive so WP05 can develop against the declared signatures. - Marker removal at integration (orchestrator, at consolidation — NOT WP06): as each fix lands, its
tests/terminus/xfail(strict)marker is removed and the repro must XPASS→green, with captured before/after. Additionally, after WP04's strategy fix lands but BEFORE WP05, verify #4982/#4997 STILL xfail (proves WP05's lane-tip work is load-bearing, not dead code — post-tasks renata). Any child that cannot fully close staysxfail(strict)with an honest reason (SO#9). - Integration tests: blast-radius suites (
tests/merge,tests/coordination,tests/git,tests/lanes,tests/terminus) +make test-fast; each WP declares its targeted surface.
Complexity Tracking
No Charter violations — table intentionally empty.