Data Model: Implement lane-allocation integrity

Phase 1 output. No persistent schema changes; this captures the decision state the two fixes reason over.

Entities / value objects

WorkspaceContext (existing — src/specify_cli/workspace/context.py)

Persisted at .kittify/workspaces/<slug>-lane-<id>.json. Read by the #4889 detector; never mutated on the fail-closed path (FR-003).

FieldMeaning for this mission
branch_namethe lane branch the detector reports as missing (e.g. kitty/mission-<slug>-lane-a)
base_committhe SHA the lane was cut from; recovery anchor
lane_idlane the WP belongs to
current_wp / wp_idactive WP
created_atprior-allocation timestamp (presence ⇒ the lane was allocated before)

Existence of this record for a lane is the load-bearing "this lane was allocated before" signal.

Lane allocation route (existing — allocate_lane_worktree)

Ordered decision. #4889 inserts a pre-flight before the FRESH routes.

WP lane state (existing — status.events.jsonl reduced)

Non-terminal post-allocation set the detector keys on: {in_progress, blocked, for_review, in_review}. Terminal (done, canceled) and pre-allocation (planned, claimed) are not triggers.

#4889 destroyed-lane decision table

Inputs: WT = lane worktree exists; BR = local lane branch exists; CTX = persisted WorkspaceContext exists; STATE = canonical WP lane state; REACH = the cut-from base (WorkspaceContext.base_commit, a proxy for the lane's work tip — the work tip is not persisted) is an ancestor of the target branch.

WTBRCTXSTATEREACHRoute / outcome
yes——any—REUSE (no-op resume) — unchanged (FR-004)
noyes—any—CRASH_RECOVERY (re-attach) — unchanged (FR-004)
nononoany—FRESH — genuinely new lane, create normally (FR-001 no false positive)
nonoyesterminal (done/canceled)—normal routing (not a trigger)
nonoyesnon-terminalyesresume/recreate — tip already on target, no work stranded (FR-009)
nonoyesnon-terminalnoFAIL CLOSED (FR-002): exit non-zero, diagnostic names branch_name + recovery ref, no Lane worktree ready, CTX untouched (FR-003)

The last row is the #4889 defect condition. The guard sits inside allocate_lane_worktree so both the CLI (create_lane_workspace) and orchestrator-api (_resolve_start_workspace) callers are covered (FR-008).

#4905 coord-commit path partition

At the shared sink _commit_via_coordination_transaction (cli/commands/agent/workflow.py), each staged path is classified before it is written into the coord worktree:

Path kind (mission_runtime.is_primary_artifact_kind)ExampleDestination
PRIMARY-partition (WORK_PACKAGE_TASK, spec/plan/tasks)kitty-specs/<slug>/tasks/WP01-*.mdPRIMARY target (via commit_to_primary_target) — never coord
STATUS_STATE (coord-owned)status.events.jsonl, status.jsoncoordination branch

Applies uniformly to all three staging sites (claim / resume-refresh / review-claim) because they all funnel through this sink (FR-005). Result: a lane cut from the coord tip no longer carries the WP file, so the FR-009 _merge_recorded_planning_commit on the next lane has no add/add conflict (FR-006).

Invariants

  • INV-1: implement never reports success (Lane worktree ready, exit 0) when the WP's committed work is unreachable from the produced lane.
  • INV-2: the coordination branch tree contains zero tasks/WP*.md blobs at any point in the lifecycle.
  • INV-3: the fail-closed path is side-effect-free w.r.t. persisted lane metadata (recovery pointer preserved).