Research: Cutover guard exempts pre-accept Missions

R1 — Fix direction

  • Decision: guard-side exemption in the shared cut-over predicate.
  • Rationale: the stamp is deferred to accept on purpose (#2917, FR-004 of runtime-state-birth-cutover-all-paths-01KYH654), and status_phase >= 1 also enables the frontmatter lane mirror (src/specify_cli/status/emit.py _legacy_lane_mirror_enabled).
  • Alternatives: birth stamp (rejected: second writer, mirror coupling); message-only stopgap (rejected: leaves the P0 red).

R2 — What counts as legacy runtime

  • Decision: a new LegacyWPRuntime.has_legacy_claim_runtime() — shell_pid, shell_pid_created_at, assignee, completed review override. agent and tracker_refs are deliberately excluded: tasks-packages step 4a writes agent at planning time and authored WPs carry tracker_refs (operator ruling of 2026-10-07; the first cut counted both via has_frontmatter_runtime() and left natively-born Missions red).
  • Rationale: has_evictable_state() includes tasks.md subtask checkboxes, present in every native Mission; move-task is event-only since the IC-04 flip, and the WP template ships empty claim fields that the reader maps to None.
  • Alternatives: reuse has_evictable_state() (rejected: exemption never fires); a separate reader (rejected: C-003).

R3 — Terminal evidence

  • Decision: non-empty accepted_at / merged_at, non-null mission_number.
  • Rationale: a superset of the fields migrate backfill-wp-status uses (accepted_at, merged_at via resolve_terminal_evidence): the guard adds mission_number and the accept_commit / merged_commit / acceptance_history markers, and treats a malformed accepted_at/merged_at as undecidable; accept writes accepted_at (mission_metadata.py record_acceptance) and stamps best-effort, so "accepted, not stamped" is reachable and must stay red.

R4 — Second consumer

  • Decision: eligible_runtime_missions excludes exempt Missions.
  • Rationale: tests/specify_cli/migration/test_dogfood_corpus_backfilled.py reaches the invariant through assert_birth_invariant_holds, which checks status_phase directly and never calls is_cut_over.

R5 — Corpus baseline

  • 2026-10-06 on this branch: 576 PASS, 5 FAIL (absent mission_id), 0 hitting the phase branch. NFR-001 is therefore proven by a synthetic matrix, with the corpus count as a no-drift check.