Implementation Plan: Declaration-first repo_root/branch resolution entry (Increment 1)

Branch: kitty/mission-declaration-first-repo-root-resolution-01M4TB21 | Date: 2026-10-13 | Spec: spec.md Input: Mission specification from kitty-specs/declaration-first-repo-root-resolution-01M4TB21/spec.md; grounding research/code-grounding.md; ADR docs/adr/4.x/2026-10-10-1.

Summary

Introduce one typed, declaration-first producer of a mission-scoped command's (repo_root, target_branch) target at the command-entry layer (specify_cli), built on the existing public resolvers. The repo_root half collapses worktree→primary (already-canonical, CWD-invariant); the target_branch half is declaration-first (meta.json target_branch) with an explicit, validated, provenance-marked primary-branch fallback that never reads the ambient checked-out HEAD. Route a bounded, named set of mission-scoped command-entry seeds through it; fold the "never Path.cwd()" assertions at those sites into the typed call so the invariant holds by construction; prove the behaviour correction red-first; preserve #3128/#3328 fail-closed; file-and-link the deferred sites.

Technical Context

Language/Version: Python 3.11+ Primary Dependencies: stdlib (pathlib, dataclasses, enum); existing specify_cli.core.paths + specify_cli.core.git_ops resolvers; mission_runtime placement seam (consumed, not modified). Storage: reads meta.json (target_branch) via the fail-closed reader read_target_branch_from_meta. No new persisted state. Testing: pytest unit tests over tmp_path git fixtures (mirror test_lifted_cli_project_root_resolution.py's worktree-fixture shape); red-first behaviour test; negative tests over enforce_checkout_identity / resolve_checkout_identity. Target Platform: Linux/macOS/Windows CLI. Project Type: single project (CLI library). Performance Goals: ≤ one extra meta.json read on the resolution path; CLI < 2 s. Constraints: module chain … <- mission_runtime <- specify_cli; no loosening of test_no_write_side_rederivation.py; ruff/mypy clean; complexity ≤ 15. Scale/Scope: one new specify_cli.core module + a bounded set of routed command-entry sites.

Charter Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

single-authority readers (read_target_branch_from_meta, resolve_canonical_root/ get_main_repo_root, resolve_primary_branch); it reconciles/extends, it does not add a second authority. It is the dual the ADR calls for.

package-set or layer-rule change (NFR-001). The mission_runtime write-handle is the deferred Increment 2.

the typed entry's branches are unit-tested directly (SC-003).

itself the constructed boundary the ADR Confirmation clause + Standing Order #5 ask for; test_no_write_side_rederivation.py stays green by construction (NFR-002).

(C-004); Mission (not Feature).

assertion fold) precede the red-first behaviour-correcting change.

  • Single canonical authority (✓): the entry is ONE producer built on the existing
  • Architectural alignment (✓): lives in specify_cli, imports only down-chain; no
  • ATDD / red-first (✓): the behaviour correction is committed test-first (FR-006);
  • Architectural gate discipline (✓): no new gate, no allowlist addition; the entry is
  • Terminology canon (✓): primary/merge/routing senses named in code + prose
  • Tidy-first sequencing (✓): behaviour-preserving enabler commits (entry, routing,
  • No full heavy-suite runs (✓): calibrated blast radius only; CI owns the full suites.

No Charter violation requires a Complexity-Tracking entry.

Project Structure

Documentation (this mission)

kitty-specs/declaration-first-repo-root-resolution-01M4TB21/
├── spec.md
├── plan.md                       # this file
├── research/code-grounding.md    # grounding (committed pre-spec)
├── tracers/{approach,design-decisions,tooling-friction}.md
└── tasks.md                      # Phase 2 output (spec-kitty tasks / finalize-tasks)

Source Code (repository root)

src/specify_cli/core/
└── command_target.py             # NEW: the typed declaration-first entry

src/specify_cli/cli/commands/     # routed command-entry sites (bounded set; see IC-3)
  (+ specify_cli/cli/helpers.py if the repo-root half composes with get_project_root_or_exit)

tests/specify_cli/core/
└── test_command_target.py        # NEW: unit coverage of every resolver branch
tests/specify_cli/cli/            # red-first behaviour test + per-routed-site tests
tests/specify_cli/workspace/ , tests/mission_runtime/   # negative fail-closed tests (#3128/#3328)

Structure Decision: Single project. The entry is a new specify_cli.core module (it depends on core.paths/core.git_ops, so it cannot live in mission_runtime without inverting the chain — see design-decisions tracer D1). Routed sites are existing cli/commands/** modules.

Design: the typed entry (concrete)

# src/specify_cli/core/command_target.py  (shape — final names settled in implement)
class TargetProvenance(Enum):
    DECLARATION = "declaration"              # target_branch read from meta.json
    VALIDATED_FALLBACK = "validated_fallback"  # explicit configured-primary fallback

@dataclass(frozen=True)
class CommandRepoTarget:
    repo_root: Path          # canonical primary root (worktree -> primary collapsed)
    target_branch: str       # declaration-first resolved branch
    provenance: TargetProvenance

def resolve_command_repo_target(
    mission_slug: str, *, cwd: Path | None = None, repo_root: Path | None = None,
) -> CommandRepoTarget: ...

Resolution contract (the invariant, held by construction): 1. repo_root — if repo_root is passed, get_main_repo_root(repo_root); else resolve_canonical_root(cwd or Path.cwd()). The single sanctioned ambient read is the cwd or Path.cwd() default here, once, documented as the one boundary; the result is always the canonical primary (worktree→primary collapse), never a raw worktree. 2. target_branch — read_target_branch_from_meta(repo_root/"kitty-specs"/mission_slug); if present → provenance=DECLARATION. If absent → resolve_primary_branch(repo_root, bias=False) → provenance=VALIDATED_FALLBACK. The entry never reads the ambient checked-out HEAD as the target (no bias=True, no get_current_branch as target) — so "the ambient branch never wins when it disagrees with the declaration" is true by construction, not by a downstream guard. 3. fail-closed — a corrupt meta.json propagates MissionMetaReadError; a cwd with no git marker propagates WorkspaceRootNotFound. No silent primary default.

Why it keeps test_no_write_side_rederivation.py green (NFR-002): it resolves branch strings only (constructs no CommitTarget/safe_commit, so the AST grammar never fires) and adds no get_current_branch(...) or <fallback> / coord_branch or current token grammar — it routes through the existing public resolvers, exactly the fix the ratchet prescribes.

Implementation Concern Map

> Concerns, not work packages. spec-kitty tasks translates these into WPs.

command_target.py: the frozen value, the provenance enum, the resolver composed on the public readers. Pure, directly unit-testable. Tidy-first enabler.

Selection criteria (applied per-site at implement, after reading each): route a site iff (a) it is mission-scoped (operates on a named/stored Mission, not project-global), (b) it today seeds repo_root/branch from ambient location, (c) routing is behaviour-preserving except in the ambient-disagreement case (the intended correction), and (d) it is not a consolidation/branch-integration site, not the implement_claim #2453-deferred ratchet-allowlisted guard, not create-time, not an honest-invocation-identity input. Candidate set (from the similar-surfaces squad, C.1): cli/commands/decision.py prologues, agent/issue_verdict.py:362, verify.py, context.py:96, and the specify_cli/mission.py facade root seeds. Each routed site's hand-placed "never Path.cwd()" comment is replaced by the typed call. Tidy-first enabler (behaviour-preserving at routed sites).

C-002/C-003). One red-first test witnessing a routed mission-scoped command resolving to the declaration (not ambient) when they disagree; negative tests proving enforce_checkout_identity (#3128) and resolve_checkout_identity (#3328) still refuse, and that get_status_read_root's worktree-preferring READ contract is untouched. Behaviour-correcting change — committed test-first, AFTER the IC-1/IC-2 enablers.

(merge-target mission_accept_merge, implement_claim #2453 guard, create-time, project-scoped, identity inputs) in the PR's Deferred section + a follow-up issue; Refs the still-open siblings #2802 and #5114 the entry forecloses.

  • IC-1 — The typed value + resolver (FR-001/002/003, NFR-001/002/003/004). The new
  • IC-2 — Route the bounded command-entry seeds + fold assertions (FR-004/005, C-004).
  • IC-3 — Red-first behaviour correction + fail-closed preservation (FR-006/007,
  • IC-4 — Deferred-scope hygiene (FR-008). Name the filed-and-linked out-of-scope sites

Rebase cadence

Onto the canonical remote's main after spec, after plan, after tasks (before implement); thereafter only on a staleness notice from CI or the operator.

Complexity Tracking

No Charter Check violations — table intentionally empty.