Mission Specification: Runtime-State Birth-Cutover (All Landing Paths)

Mission Branch: fix/runtime-state-birth-cutover-all-paths Created: 2026-07-27 Status: Draft Input: Structural closure of #2917 — the runtime-state birth-cutover seam only fires for one of the two ways a mission can land on main, so the dogfood corpus re-drifts red and re-blocks the release tag on every GitHub-side merge wave.

Context & Background

Every mission records its lifecycle runtime state as an append-only event log (status.events.jsonl). A mission counts as cut over to the event-sourced model only when its committed corpus carries both a stamped meta.json status_phase = "1" and the deterministic seed events that make verify_backfill pass (the reduced snapshot equals the legacy reader by count and value). A release-gating acceptance test (tests/specify_cli/migration/test_dogfood_corpus_backfilled.py) fails on main whenever any eligible mission in the committed corpus is not cut over.

#2920 introduced a birth-cutover stamp so missions land already cut over — but it fires only inside spec-kitty merge (the local lane-consolidation path). The maintainer lands missions on main through GitHub-side squash/rebase merges, which execute no Spec Kitty code, so those missions arrive un-cut-over. This was proven three independent ways on 2026-07-27 (three 01KW6N missions, this programme's predecessor #2963's own mission, and six recent 01KYE/01KYF* missions missing the status_phase key). The mechanical re-green shipped as hotfix #2968; this mission removes the structural cause so the drift cannot recur, plus a fail-closed guard so it can never again reach main silently.

Domain Language (canonical terms)

deterministic seed events, so it passes verify_backfill.

verify_backfill.

spec-kitty merge (local lane consolidation) or GitHub-side squash/rebase. Ambiguous synonym to avoid: bare "merge" (see charter footgun note).

  • Birth-cutover: stamping a mission's corpus with status_phase = "1" and the
  • Cut over: the state of a mission whose committed corpus passes
  • Landing path: the mechanism by which a mission's corpus reaches main
  • Drift: a mission that reaches main not cut over.

User Scenarios & Testing (mandatory)

User Story 1 - A mission is born cut over regardless of how it lands (Priority: P1)

When a mission's work is complete and its corpus is finalized, the cutover stamp is written into the mission branch as a committed artifact before the branch can land — so whether the maintainer lands it via spec-kitty merge or a GitHub-side squash/rebase, the corpus that arrives on main is already cut over.

Why this priority: This is the structural root. Without it, every GitHub-merged mission re-introduces drift and re-blocks the release tag, requiring a manual re-green each time.

Independent Test: Take a mission finalized through the lifecycle, land it via a GitHub-style squash merge (no spec-kitty merge), and assert its corpus on the target branch is cut over (verify_backfill passes) with no manual step.

Acceptance Scenarios:

1. Given a mission whose runtime work is final, When it reaches the terminal lifecycle point, Then its committed corpus carries status_phase = "1" and deterministic seed events that pass verify_backfill. 2. Given a mission whose corpus is already cut over, When it lands via a GitHub-side squash/rebase merge, Then the corpus on the target branch is cut over and test_dogfood_corpus_backfilled passes. 3. Given a mission landed via spec-kitty merge, When it reaches the target branch, Then it is cut over exactly as before (no regression to the existing path).

User Story 2 - Un-cut-over missions are blocked before they can reach main (Priority: P1)

A fail-closed pre-merge check rejects any change set that would introduce (or leave) an un-cut-over mission in the corpus, naming the offending mission(s) and the exact remedy, so drift can never land silently even if the stamp is bypassed.

Why this priority: Defense in depth. The stamp (US1) prevents the common case; the guard makes the invariant enforceable and makes any future gap loud instead of silent-until-release.

Independent Test: Construct a pull request whose diff contains an un-cut-over mission corpus and assert the guard check fails the PR with a message naming the mission and the remedy command; construct one where all touched missions are cut over and assert it passes.

Acceptance Scenarios:

1. Given a PR whose changed corpus contains an un-cut-over mission, When the pre-merge guard runs, Then it fails and names the mission(s) plus the remedy command. 2. Given a PR where every touched mission is cut over, When the guard runs, Then it passes. 3. Given the guard encounters a verify error or an ambiguous corpus, When it evaluates, Then it fails closed (blocks), never passes on uncertainty.

User Story 3 - The corpus stays reconciled and drift is observable (Priority: P2)

After this mission lands, the dogfood corpus acceptance test stays green on main across merge waves with no manual intervention, and an operator can list un-cut-over missions on demand.

Why this priority: Turns the fix into a durable, observable guarantee rather than a one-time repair.

Independent Test: Over consecutive merge waves, test_dogfood_corpus_backfilled stays green on main with zero manual re-green; an on-demand audit reports the cut-over status of every mission.

Acceptance Scenarios:

1. Given the mission has landed, When subsequent missions land via any path, Then test_dogfood_corpus_backfilled remains green on main with no manual re-green. 2. Given an operator wants to audit the corpus, When they request cut-over status, Then they get a deterministic report of any un-cut-over missions.

Edge Cases

prematurely (a stamp written while implement still dual-writes runtime to frontmatter/tasks.md would pass vacuously and then red verify_backfill once work advances). The stamp fires only when runtime state is final.

runtime state to seed) must not be forced to a false stamp and must not be flagged by the guard.

(idempotent; never appends duplicate or divergent seed events).

writers that can disagree.

be treated as un-cut-over by the guard.

lifecycle stamp — directly-authored corpus (e.g. the #2968 hotfix pattern), abandoned/cancelled missions, mid-flight merges, or a later mission's PR that edits an earlier mission's corpus. The auto-stamp cannot cover these; the guard is the binding backstop and must evaluate every mission whose corpus appears in the PR diff, not just the "current" mission.

to seed, so verify_backfill returns ok=True, wp_count=0 — vacuously green. "Cut over" for such a mission must be established from event-log evidence + non-empty snapshot + status_phase = "1", never from verify_backfill.ok alone (which is necessary-but-not-sufficient and would green-wash the exact missions this mission targets, and mask a genuinely broken event log).

commit; the seam must commit both partitions (meta.json on PRIMARY, seed events on COORD) into the branch that lands, with no reliance on a background status daemon's auto-commit (which can commit under a stale message).

different leg contexts can leave status_phase = "1" while verify_backfill later reds on payload divergence — the worst state. Anchor resolution must be pinned to one canonical leg.

  • A mission whose runtime work is still in progress must not be stamped
  • A mission that is genuinely excluded from cutover eligibility (e.g. no
  • A mission already cut over must be a no-op for both the stamp and the guard
  • The two landing-path stamps must be one authority, not two divergent
  • A mission landed with the status_phase key absent (not merely null) must
  • Seam-bypass paths: a corpus can reach main without passing the terminal
  • Vacuous-green trap: a natively-born mission has no retired-frontmatter state
  • Write-vs-commit: the stamp writes meta.json/seed events but performs no git
  • Flipped-but-unverifiable: two stamps resolving the claim anchor from

Requirements (mandatory)

Functional Requirements

IDTitleUser StoryPriorityStatus
FR-001Stamp cutover at the terminal lifecycle pointAs a maintainer, I want a mission's cutover stamped into its branch when its runtime work is final, so the corpus is already cut over no matter how the branch later lands.HighOpen
FR-002Fail-closed pre-merge guardAs a maintainer, I want CI to reject any change set that would leave an un-cut-over mission in the corpus, so drift cannot reach main silently.HighOpen
FR-003Actionable guard diagnosticsAs a maintainer, I want the guard to name the offending mission(s) and print the exact remedy command, so I can re-green in one step.HighOpen
FR-004Stamp only when runtime state is finalAs a maintainer, I want the stamp withheld while a mission's runtime state is still mutating, so it never produces a vacuously-green stamp that reds later.HighOpen
FR-005Single cutover authority across landing pathsAs a maintainer, I want the spec-kitty merge stamp and the new lifecycle stamp to share one implementation, so the two paths cannot diverge.HighOpen
FR-006Idempotent stamp and guardAs a maintainer, I want stamping/guarding an already-cut-over mission to be a no-op, so re-runs never corrupt the event log.MediumOpen
FR-007On-demand cut-over auditAs an operator, I want to list every mission's cut-over status on demand, so I can verify corpus health outside CI.MediumOpen
FR-008Guard is wired to fire on corpus-only PRs as a required checkAs a maintainer, I want the pre-merge guard to actually trigger when a PR changes only kitty-specs/**, and to be a required, non-skippable status check, so a corpus-only mission PR cannot merge without the guard executing.HighOpen
FR-009Guard evaluates every mission in the diff, keyed on event-log evidenceAs a maintainer, I want the guard to check every mission whose corpus appears in the PR diff — using the acceptance test's event-log-evidence eligibility + non-empty-snapshot + status_phase check, not bare verify_backfill — so natively-born missions (no frontmatter to seed) cannot pass vacuously.HighOpen

Non-Functional Requirements

IDTitleRequirementCategoryPriorityStatus
NFR-001No manual re-greenAfter this mission lands, test_dogfood_corpus_backfilled passes on main across at least 3 consecutive merge waves with zero manual re-green interventions.ReliabilityHighOpen
NFR-002Bounded guard costThe pre-merge guard evaluates only missions touched by the change set (diff-scoped) and completes in under 30s in CI on the current corpus. The full-corpus acceptance lock (NFR-001) is a separate, slower check.PerformanceMediumOpen
NFR-003Fail-closed by defaultOn any verify error, missing artifact, ambiguity, or absent mission_id at stamp time, the guard/stamp blocks (non-zero exit); it never passes on uncertainty and never falls back to slug-namespaced seed identity.SafetyHighOpen
NFR-004Deterministic stamp (payload-level)The stamp produces byte-identical seed events — identity and payload (including the resolved claim anchor) — for identical mission input across runs, machines, and landing-path contexts. Anchor resolution is pinned to one canonical leg so the two stamp callers cannot diverge.ReliabilityHighOpen

Constraints

IDTitleConstraintCategoryPriorityStatus
C-001Respect the PR workflowThe guard operates pre-merge on pull requests; the stamp commits into the mission branch. No mechanism may push directly to origin/main.TechnicalHighOpen
C-002Phase-2 model onlyOnly status_phase = "1" (event-sourced) is written; the retired frontmatter lane mirror must not be reintroduced.TechnicalHighOpen
C-003Do not rewrite migrated corpusThe change must not alter the ~319 already-cut-over missions; only un-cut-over missions are affected.TechnicalHighOpen
C-004No spec-kitty execution at GitHub-merge timeThe design must not assume any Spec Kitty command runs at GitHub-side merge time; the cutover must already be committed in the branch, enforced by CI.TechnicalHighOpen
C-005Scope boundaryOut of scope: read-side placement (#2922), write-target consolidation (#2966 parts B/C), terminology migration (#2964). The mechanical re-green (#2968) is already shipped and must not be redone.BusinessMediumOpen

Key Entities

(status_phase) and status.events.jsonl (event log) — that the acceptance test evaluates.

make verify_backfill pass.

legacy reader by count and value.

mission is cut over before merge.

  • Mission corpus: the committed kitty-specs/<mission>/ artifacts — meta.json
  • Cutover stamp: status_phase = "1" plus the deterministic seed events that
  • verify_backfill: the parity check asserting the reduced snapshot equals the
  • Pre-merge guard: the fail-closed CI check enforcing that every touched

Success Criteria (mandatory)

Measurable Outcomes

cut over (0 un-cut-over over at least 3 post-mission merge waves).

blocked by CI before merge (0 false negatives across the guard's test set).

following this mission.

after this mission lands.

  • SC-001: 100% of missions that land on main — via either landing path — are
  • SC-002: Every pull request that would introduce an un-cut-over mission is
  • SC-003: 0 manual re-green interventions are required in the release cycle
  • SC-004: test_dogfood_corpus_backfilled stays green on main continuously

Assumptions

2026-07-27): stamp the cutover into the branch at a terminal lifecycle point and enforce with a pre-merge CI guard. The exact terminal seam (e.g. accept / mission completion) and guard host are plan-phase decisions.

(single authority) — the same function merge/executor.py::_run_birth_cutover already calls — rather than a second implementation.

and birth-invariant body (_mission_carries_event_log_runtime / _assert_birth_invariant_holds), not bare verify_backfill (which is vacuous for natively-born missions). verify_backfill remains part of the check but is necessary-not-sufficient.

#2968; with the auto-stamp preventing new drift and the diff-scoped guard blocking any that slips, the full-corpus acceptance lock stays green on main without a further one-time backfill.

cutover_repo(dry_run=True), matching the existing per-subcommand-module doctor convention.

branch, pre-PR, already commits residual acceptance artifacts); the exact seam and guard-host workflow are validated in the plan, including a live scratch-PR check that the guard fires on a corpus-only diff (per the #2968 lesson that path filters must be verified, not read).

  • The confirmed approach is auto-stamp + fail-closed guard (operator decision,
  • The stamp reuses the canonical runtime_state_cutover.cutover_mission helper
  • The pre-merge guard reuses the acceptance test's event-log-evidence eligibility
  • The pre-existing corpus backlog was already re-greened whole-corpus by hotfix
  • The on-demand audit (FR-007) is a spec-kitty doctor subcommand backed by
  • Confirmed seam candidate is the terminal accept step (runs on the mission