Data Model: Consistent Mission-Handle Resolution

This mission introduces no persistent schema and no migration. The only new in-memory structure is the listing surfaced by discovery.

Entities / Value Objects

MissionListing (new, in-memory)

One row per existing mission, produced by list_missions_for_selection(main_repo) (name avoids the mission.py:809 discover_missions collision) and rendered by the missing-handle discovery path.

FieldTypeSourceNotes
mission_slugstrmission directory / metaThe copy-pasteable handle.
mid8str \Nonemeta.json mission_id[:8]
friendly_namestrmeta.json friendly_name → slug fallbackHuman title; read from meta, not on ResolvedMission. Falls back to the slug when absent so the listing never blanks.

spec.md or meta.json — the SAME rule the plan/tasks auto-detect uses (_list_feature_spec_candidates), so next and plan/tasks never disagree on the count. This deliberately includes legacy missions without a mission_id (which FsMissionResolver.all_missions() would silently drop).

one-line "N mission(s) need spec-kitty migrate backfill-identity".

tests are reproducible.

  • Population rule (decided post-squad): count any mission directory bearing
  • Legacy nudge: when ≥1 listed mission lacks a mission_id, the render adds a
  • Ordering: stable and deterministic (by mission_slug) so listings and

DiscoveryOutcome (new, in-memory)

The resolution of a missing --mission on next.

VariantTriggerEffect
auto_selected(slug)exactly 1 missionresolve to that slug, proceed
needs_choice(listings)>1 missionsrender listings, clean non-usage exit
none_found0 missionsrender setup nudge, non-usage exit

Existing types referenced (unchanged)

mission_slug, feature_dir, mid8. Not modified.

— the ambiguous-selector and not-found signals. Behavior preserved; the not-found message text is unified via the canonical template.

user sees for a nonexistent mission passed to merge (it stays correct for a real-but-unfinalized mission).

  • ResolvedMission (context/mission_resolver.py) — mission_id,
  • AmbiguousHandleError / MissionNotFoundError (context/mission_resolver.py)
  • MissingLanesError (lanes/persistence.py) — must no longer be the surface a

State transitions

None. This mission changes error/branching behavior only; no mission lifecycle state is added or altered. Auto-selection resolves a handle but does not change the selected mission's runtime state.