Contracts

lane-work-tip.md

Contract: lane work tip

Ref

  • Name: refs/spec-kitty/lane-tip/<full-lane-branch-name>
  • Scope: local only; never pushed.

Recorder hook

The hook is post-commit, written in POSIX sh, and installed at git rev-parse --git-path hooks by policy/hook_installer.py.

#!/bin/sh
# Generated by spec-kitty (lane-tip recorder). Do not edit by hand.
b=$(git symbolic-ref -q HEAD) || exit 0
case "$b" in refs/heads/kitty/mission-*-lane-*) ;; *) exit 0 ;; esac
id=${b##*-lane-}
case "$id" in ''|*[!a-z]*) exit 0 ;; esac
git update-ref "refs/spec-kitty/lane-tip/${b#refs/heads/}" HEAD >/dev/null 2>&1
exit 0

Rules:

  • The hook is installed only if the slot is empty or already carries the spec-kitty signature. Otherwise the installer warns and leaves the slot alone (C-010).
  • It always exits 0 and never starts Python (NFR-001).

API (specify_cli.lanes.lane_tip)

FunctionReturns / effect
tip_ref(branch) -> strthe ref name for the lane branch
`record_tip(repo_root, branch, sha=None) -> str \None`
`read_tip(repo_root, branch) -> str \None`
clear_tip(repo_root, branch) -> Nonedeletes the ref
is_absorbed(repo_root, tip, target, base) -> boolraises AbsorptionUnsupported when git is too old to evaluate

is_absorbed returns True in any of three cases:

  • tip == base
  • tip is an ancestor of target
  • merge-tree --write-tree target tip equals target^{tree}

Errors

ErrorCodeWhenCarries
DestroyedLaneErrorDESTROYED_LANE (existing)a destroyed lane whose tip is not absorbedtip_sha and a remedy git branch <b> refs/spec-kitty/lane-tip/<b> (restore), or git update-ref -d refs/spec-kitty/lane-tip/<b> (deliberately abandon)
LaneWorkTipUnknownErrorLANE_WORK_TIP_UNKNOWNthe context is present but no tip ref exists—

single-branch-execution.md

Contract: single_branch execution

Owner: the lane manifest (lanes.json), read through is_repo_root_lane, and the workspace resolver.

Finalize

compute_lanes(..., topology=single_branch) returns a manifest that holds exactly one lane:

  • lane_id: lane-planning
  • wp_ids: every work package in the mission

The manifest carries the target branch as target_branch. It also carries mission_branch, but only when meta.json records one.

If assert_topology_matches_manifest(topology, manifest) fails, the manifest write raises SINGLE_BRANCH_CODE_LANES_UNMIGRATED.

Resolve

For any work package in a single_branch mission, resolve_workspace_for_wp returns:

FieldValue
resolution_kindrepo_root
worktree_patheffective_root if set, otherwise the repository root checkout
branch_namemeta.mission_branch if set, otherwise target_branch
lane_idlane-planning
status_execution_modedirect_repo

Implement: refusals

Refusals are checked in this order. Each names the blocking object and a remedy (NFR-004).

1. Unmigrated mission. Code lanes are present, so the command raises SINGLE_BRANCH_CODE_LANES_UNMIGRATED. 2. Wrong branch. The write checkout's HEAD is not the expected write branch. The error names both branches. The command never switches branches itself. 3. Another WP in progress. Another work package, from any single_branch mission, is in_progress in the same write checkout. The error names that mission and WP.

4. Dirty checkout. The write checkout has tracked changes, or untracked files outside spec-kitty-owned paths. The error lists the paths.

  • Exception: resuming the WP that is already in_progress is a no-op.
  • Exception: resume.

Claim base and for_review (post-plan fold B2)

A work package in a repo-root lane has no lane branch, so the for_review gate needs another starting point. This covers every repo-root lane WP, planning_artifact WPs included.

  • Claim: implement records refs/spec-kitty/wp-base/<mission_slug>/<WP> as the write checkout's current HEAD. On resume, it records the ref only if it is missing.
  • for_review: the gate requires at least one commit in wp-base..HEAD in the write checkout. If there are none, it refuses the move, as it does today.
  • Cleanup: the ref is deleted when the WP reaches a terminal state.

The manifest's mission_branch for a single_branch mission is meta.mission_branch when set, otherwise target_branch.

Implement: no dependency merge

Implement skips _merge_dependency_lane_tips for the repo-root lane. Dependency readiness (approved / done) is still enforced.

Consolidate

CaseBehaviour
Unprotected target, or commit_to_targetBookkeeping only. No git landing and no branch deletion.
Protected targetLands mission_branch onto target_branch through the mission→target phase. Authored blobs come from first-parent base..mission_branch. Checks out target_branch in the write checkout, then deletes mission_branch. Never removes the repository root checkout or target_branch.

topology-restamp.md

Contract: topology re-stamp migration and doctor finding

Migration m_4_0_0rc5_single_branch_code_lanes_restamp

PropertyValue
MIGRATION_ID4_0_0rc5_single_branch_code_lanes_restamp
TARGET_VERSION4.0.0rc5
runs_on_worktreesFalse

Behaviour:

  • Selection. Walks the kitty-specs/*/ directories on the checked-out branch. A mission is selected when read_topology(meta) == single_branch and has_code_lanes(lanes.json).
  • Write. For each selected mission it writes topology: lanes through the canonical meta writer, and changes no other field.
  • No commit. The migration does not commit. The upgrade auto-commit owns that.
  • Idempotent. A second run changes nothing (NFR-003).
  • Operator CLI. spec-kitty migrate backfill-topology --restamp-single-branch [--dry-run] calls the same restamp_single_branch_with_code_lanes().

Doctor

spec-kitty doctor topology adds per-mission finding rows:

FindingCarries
SINGLE_BRANCH_CODE_LANES_UNMIGRATEDthe mission slug and the remedy
(none)clean single_branch missions produce no row