Implementation Plan: Worktree-Owned Root for Mission Create/Next
Branch: fix/worktree-owned-root-3328-v2 | Date: 2026-08-11 | Spec: kitty-specs/worktree-owned-root-3328-01KZRG01/spec.md Input: Feature specification from kitty-specs/worktree-owned-root-3328-01KZRG01/spec.md
Summary
mission create and next cannot explicitly target the invoking linked worktree as the mission's owned checkout: mission create refuses unconditionally on worktree invocation (is_worktree_context(Path.cwd()), disconnected from the caller's actual repo_root), and next either refuses (.worktrees-literal paths) or silently redirects through the ambiently-resolved primary checkout (generic linked worktrees). This plan adds a single, shared, git-topology-validated checkout ownership primitive that both commands consult through a new, explicit, named CLI affordance — never through allow_worktree_context — and threads the validated owned checkout through safe_commit and per-checkout runtime-state paths so mission-create writes and next's runtime bookkeeping land in the owned worktree, not the ambient primary. Every caller that does not opt in keeps today's exact behavior (research D-1/D-2; spec C-001/C-002/FR-004).
Technical Context
Language/Version: Python 3.11+ (repo baseline; no new runtime dependency) Primary Dependencies: none new — reuses subprocess (git shell-outs already used by commit_helpers._is_worktree_of and coordination.surface_resolver.read_worktree_registry) Storage: N/A (no persisted business data; see data-model.md for the checkout-ownership domain model) Testing: pytest; existing tests/architectural/, tests/runtime/, tests/agent/, tests/contract/, tests/unit/workspace/ suites plus new ATDD suite invoking the real installed CLI in two real git worktree add checkouts Target Platform: Cross-platform (Linux/macOS/Windows per DIR-001) — git rev-parse/git worktree list --porcelain output is platform-uniform; path comparisons use Path.resolve() throughout, consistent with existing _is_worktree_of Project Type: Single project (CLI + runtime library) — no frontend/backend split Performance Goals: NFR-001 — at most one additional git rev-parse subprocess per mission create/next invocation versus current baseline Constraints: C-001 (fail-closed default preserved), C-002 (no ambient fallback for the new path), C-004 (immutable-artifact validation only), C-006 (generic linked-worktree recognition) Scale/Scope: Touches ~6 existing modules (core/mission_creation.py, core/paths.py or a new sibling module, git/commit_helpers.py, cli/commands/next_cmd.py, cli/commands/agent/mission_create.py, coordination/surface_resolver.py) plus one new module and one new ADR; no schema/data migration.
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Single canonical authority: the plan introduces exactly ONE new checkout-ownership validation primitive (IC-01) that both
mission createandnextconsult — it does not add a seventh independent common-dir comparator (research D-2/D-4 identified the existing sprawl; this plan reduces divergence risk by reusing_is_worktree_of's comparison logic, not duplicating it). - Architectural alignment: builds on existing precedent (
BookkeepingTransaction'srepo_root/worktree_rootsplit,get_status_read_root's read-vs-write naming convention) rather than inventing a parallel topology. - ATDD-first: FR-012's real two-worktree, real-installed-CLI concurrency test is written and confirmed RED (refused, matching today's behavior) before any production code changes (IC-06/IC-07 precede IC-02/IC-03 completion in the dependency graph — see tasks.md).
- Locality of change: no unrelated refactor of the 4 independent root-resolvers (D-2) or the 6+ independent common-dir call sites (D-4 summary) — those are pre-existing sprawl this mission narrows contact with, not eliminates wholesale (that broader consolidation is out of scope; C-003).
- Terminology canon: "Mission" used throughout; no "feature" reintroduced in new identifiers.
- No violations requiring Complexity Tracking justification.
Project Structure
Documentation (this mission)
kitty-specs/worktree-owned-root-3328-01KZRG01/
├── plan.md # This file
├── research.md # Phase 0 output (completed)
├── data-model.md # Phase 1 output (completed) — checkout-ownership domain model
├── quickstart.md # Phase 1 output — manual verification walkthrough
├── contracts/ # Phase 1 output — CLI/API contract for the new ownership affordance
└── tasks.md # Phase 2 output (/spec-kitty.tasks — not created by /spec-kitty.plan)
Source Code (repository root)
src/specify_cli/core/
├── checkout_ownership.py # NEW — OwnershipClaim, OwnershipValidationResult, resolve_ownership_claim()
├── mission_creation.py # MODIFIED — accept new explicit-ownership param, thread into safe_commit
└── paths.py # UNCHANGED read-path resolvers; may export a small shared helper checkout_ownership.py reuses
src/specify_cli/git/
└── commit_helpers.py # UNCHANGED public signature; _is_worktree_of's internal comparator becomes the reused primitive (imported, not duplicated)
src/specify_cli/coordination/
└── surface_resolver.py # MODIFIED — expose the raw registry-based nested-worktree check (generic, not .worktrees-literal) for checkout_ownership.py to consume
src/mission_runtime/
├── resolution.py # MODIFIED — create-time target seam plus explicit owned-root mission-context threading
└── __init__.py # MODIFIED — export the canonical seam through the umbrella API
src/specify_cli/cli/commands/
├── next_cmd.py # MODIFIED — validated owned root remains explicit through mission resolution
└── agent/mission_create.py # MODIFIED — new CLI option, threads into create_mission_core()
src/runtime/next/
├── decision.py # MODIFIED — explicitly thread the validated owned root; no ambient inference
├── runtime_bridge.py # MODIFIED — resolve mission content/meta/runtime against the explicit owned root
└── runtime_bridge_io.py # UNCHANGED — existing repo-root-keyed runtime-state paths consume the threaded root
src/specify_cli/merge/
└── workspace.py # UNCHANGED signature (same rationale as above)
tests/
├── architectural/test_no_production_worktree_guard_bypass.py # MODIFIED — extended assertion or documented retirement rationale per its own docstring intent
├── architectural/test_mission_runtime_surface.py # MODIFIED — pin the canonical create-time target export in the umbrella API contract
├── core/test_checkout_ownership.py # NEW — unit tests for the validation primitive (OWNED/NESTED/FOREIGN/BROKEN_POINTER)
├── e2e/test_worktree_owned_root_concurrency.py # NEW — FR-012 ATDD: real installed CLI, two real linked worktrees, forced overlap
├── architectural/surface_resolution_audit/inventory.md # MODIFIED — re-pin WP02 effective_root callsite descriptor
├── architectural/test_single_mission_surface_resolver.py # MODIFIED — re-pin the companion resolver descriptor
├── agent/test_agent_feature.py # MODIFIED — extend existing worktree-refusal tests to assert unchanged default behavior (regression net for C-001/C-002)
└── mission_runtime/test_create_time_write_target.py # NEW — bootstrap target-seam contract and default-path non-regression
docs/adr/3.x/
├── <next-available-date>-checkout-ownership-for-mission-create-and-next.md # NEW ADR
└── index.md # GENERATED — canonical era index row
docs/development/
├── 3-2-page-inventory.yaml # GENERATED — page-inventory lockfile
└── 3-2-docs-retrieval-index.yaml # GENERATED — docs retrieval index
scripts/docs/freshen_adr_inventory.py # MODIFIED — resolve redirect-stub eras to index.md
tests/docs/test_freshen_adr_inventory.py # MODIFIED — real redirect-stub/index contract
Structure Decision: Single project, additive. No new top-level directories. The one new production module (core/checkout_ownership.py) sits beside core/paths.py and core/mission_creation.py because it is consumed by both and by next_cmd.py; placing it in core/ (rather than under cli/) keeps it importable without pulling in Typer/CLI dependencies, matching the existing layering (core/paths.py, core/context_validation.py are both dependency-light).
Complexity Tracking
No Charter Check violations — table intentionally empty.
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| — | — | — |
Implementation Concern Map
> Implementation concerns are NOT work packages. /spec-kitty.tasks translates these into executable WPs.
IC-01 — Shared checkout-ownership validation primitive
- Purpose: Provide the ONE function both
mission createandnextcall to turn an explicit ownership request into a validatedOwnershipClaim(data-model.md), reusing_is_worktree_of's fail-closed common-dir comparison andread_worktree_registry's generic (non-.worktrees-literal) nested-worktree data. - Relevant requirements: FR-003, FR-005, FR-006, FR-011, NFR-004, C-006
- Affected surfaces:
src/specify_cli/core/checkout_ownership.py(new),src/specify_cli/git/commit_helpers.py(expose_is_worktree_of's comparator for reuse — rename/export if it stays "private"),src/specify_cli/coordination/surface_resolver.py(expose raw registry entries for the ancestor/descendant nested check) - Sequencing/depends-on: none (foundation)
- Risks:
_is_worktree_ofis currently module-private (_-prefixed) insidecommit_helpers.py— exposing it for reuse must not widen its contract or breaksafe_commit's existing behavior; prefer a thin public wrapper over renaming the private function outright to minimize diff (locality of change).
IC-02 — mission create explicit-ownership integration
- Purpose: Add the new CLI affordance to
agent mission create, thread it throughcreate_mission_core()as a distinct, validated parameter (neverallow_worktree_context), and pass the resolved owned checkout asworktree_root(with the independently-resolved primary asrepo_root) intosafe_commitvia_commit_feature_file. During the create-time window before mission identity is readable from the primary checkout, resolvecreate_mission_core()'s already-derivedplanning_branchthrough one canonicalmission_runtimebootstrap target seam instead of askingplacement_seam()to infer absent metadata. - Relevant requirements: FR-001, FR-004, FR-008, FR-009, FR-010, C-001, C-002
- Affected surfaces:
src/specify_cli/cli/commands/agent/mission_create.py,src/specify_cli/core/mission_creation.py,src/mission_runtime/resolution.py,src/mission_runtime/__init__.py,tests/mission_runtime/test_create_time_write_target.py,tests/architectural/test_mission_runtime_surface.py - Sequencing/depends-on: IC-01
- Bootstrap target-seam contract:
resolve_create_time_write_target(planning_branch: str) -> CommitTargetis a pure, explicit-input constructor for this pre-readable-identity window only. It rejects empty and fully-qualifiedrefs/heads/...inputs and returns the validated short branch unchanged. It performs no CWD, environment, topology, mission-directory, or ambient-root discovery. Once mission identity is readable, all ordinary writes continue throughplacement_seam(...).write_target(...); the no-opt-in path does not call the bootstrap seam and stays byte-identical. - Risks:
mission_creation.py's existing CWD-vs-is_worktree_contextguard (line 309-314) must remain intact for non-opted-in callers (C-001) — the new path is an additional branch taken only when the new parameter is supplied, not a replacement of the existing guard's default. The bootstrap seam must not become a second general placement resolver: its narrow signature and tests forbid ambient discovery, and_commit_feature_filemay receive its target only after anOWNEDclaim binds the current exact checkout root andcreate_mission_core()has derived the explicitplanning_branchthrough its existing logic.
IC-03 — next explicit-ownership integration and per-checkout runtime-state rooting
- Purpose: Add the same CLI affordance to
next, validate it through IC-01, and rootfeature-runs.json/merge-lock-directory resolution at the owned checkout instead of the ambiently-resolved primary when ownership isOWNED. - Relevant requirements: FR-002, FR-004, FR-007, C-001, C-002
- Affected surfaces:
src/specify_cli/cli/commands/next_cmd.py, (read-only reuse, no signature change expected:src/runtime/next/runtime_bridge_io.py,src/specify_cli/merge/workspace.py) - Sequencing/depends-on: IC-01
- Risks:
next's existing@require_main_repodecorator (.worktrees-literal) must keep gating non-opted-in callers exactly as today (FR-004) — the new affordance is consulted BEFORE that decorator's refusal fires (or the decorator itself gains a narrow, explicit bypass ONLY when a validatedOwnershipClaim.OWNEDis present — a design choice to confirm during implementation, not this plan; tasks.md WP02 records the decision point).
IC-04 — Structured, distinguishable refusal errors
- Purpose: Ensure
NESTED,FOREIGN_OR_MISMATCHED,BROKEN_POINTER, and the existingUNOWNED_NO_OPT_INeach raise/report a distinguishable error (not one generic string), consumable by--jsonoutput and by harnesses branching on failure class. - Relevant requirements: FR-011
- Affected surfaces:
src/specify_cli/core/checkout_ownership.py(exception/result types),src/specify_cli/cli/commands/agent/mission_create.pyandnext_cmd.py(error rendering /--jsonpayload shape) - Sequencing/depends-on: IC-01
- Risks:
mission_create.py:_print_worktree_navigation_hint's existing substring match on"worktree"in the error message must keep working for the UNCHANGED default-refusal path (regression risk if error message wording shifts).
IC-05 — Architectural fence reconciliation
- Purpose: Confirm
tests/architectural/test_no_production_worktree_guard_bypass.pycontinues to hold forallow_worktree_context(NFR-003) while the new, distinct parameter is explicitly exempted by name (not by weakening the AST scan's target). - Relevant requirements: FR-010, NFR-003
- Affected surfaces:
tests/architectural/test_no_production_worktree_guard_bypass.py - Sequencing/depends-on: IC-02, IC-03
- Risks: none identified — this is a verification-only concern once IC-02/IC-03 land.
IC-06 — Real installed-CLI, two-linked-worktree concurrency ATDD harness
- Purpose: Build/install an immutable wheel from the reviewed commit (never editable), create two real linked worktrees via
git worktree addat generic (non-.worktrees) paths, force temporal overlap of two subprocess CLI invocations, and assert distinct mission IDs/slugs/refs/runtime state with clean trees afterward. - Relevant requirements: FR-012, NFR-002, C-004
- Affected surfaces:
src/specify_cli/cli/commands/next_cmd.py,src/mission_runtime/resolution.py,src/runtime/next/decision.py,src/runtime/next/runtime_bridge.py, newtests/e2e/test_worktree_owned_root_concurrency.py,tests/architectural/surface_resolution_audit/inventory.md, andtests/architectural/test_single_mission_surface_resolver.py. The validatedOwnershipClaim's effective checkout root is threaded explicitly through command → decision → bridge → mission-context resolution; no layer may reconstruct it from CWD or callget_main_repo_rootfor the opted-in path. The no-flag path retains the existing primary-anchor behavior byte-for-byte. Reuse an existing immutable-wheel fixture if present rather than adding a helper. - Sequencing/depends-on: written RED before IC-02/IC-03 complete (ATDD-first); final GREEN pass depends on IC-01/IC-02/IC-03
- Risks: wheel build time inflates CI duration; must confirm an existing build/install fixture isn't already present in the repo (research did not find one — tasks.md WP-level research step should re-confirm before writing a new one) to avoid duplicating packaging-fixture logic.
IC-07 — Negative/adversarial test coverage
- Purpose: Dedicated unit/integration tests for nested, foreign/mismatched-common-dir, broken-gitdir-pointer, and opt-in-without-passing-validation scenarios (spec.md Edge Cases + User Story 2).
- Relevant requirements: FR-013, NFR-004
- Affected surfaces:
tests/core/test_checkout_ownership.py(new) - Sequencing/depends-on: IC-01
- Risks: none identified.
IC-08 — ADR authoring
- Purpose: Record the new checkout-ownership validation mechanism as an architectural decision (research D-7 confirmed no existing ADR covers this).
- Relevant requirements: supports FR-001 through FR-013 collectively; T021 additionally enforces FR-011/NFR-004/C-002 for canonical, fail-closed documentation authority
- Affected surfaces:
docs/adr/3.x/<date>-checkout-ownership-for-mission-create-and-next.md(new),scripts/docs/freshen_adr_inventory.py,tests/docs/test_freshen_adr_inventory.py, and generator-only outputsdocs/adr/3.x/index.md,docs/development/3-2-page-inventory.yaml, anddocs/development/3-2-docs-retrieval-index.yaml - Sequencing/depends-on: IC-01 (document the primitive once its shape is settled), can be drafted in parallel with IC-02/IC-03 and finalized after
- Risks: date-slot collision with concurrent missions (research risk #3 — confirm the next free date-prefix slot under
docs/adr/3.x/at task-authoring time, not baked into this plan). The Common Docs move leftREADME.mdas a redirect stub while the canonical freshener still targeted it; T021 fixes that authority mismatch. A declared## Indexsection is the fail-closed signal for table-maintaining canonical landing pages, while sanctioned 1.x/2.x table-lessindex.mdpages remain skipped. Real-tree--all/--all --check, explicit malformed-target exit-2 parity, legacy fixtures, and containment guards lock that boundary. Every index/inventory byte remains delegated to sanctioned generators (#3345). Prime cycle 1 also proved the ADR must be registered in the generated docs retrieval index or blocking docs-freshness CI reportsDOCS-INDEX-DRIFT; Prime cycle 2 proved an unconditionalindex.md-without-table refusal breaks the repository's real legacy layout.