Implementation Plan: Runtime-State Birth-Cutover (All Landing Paths)
Branch: fix/runtime-state-birth-cutover-all-paths | Date: 2026-07-27 | Spec: spec.md Input: Feature specification from kitty-specs/runtime-state-birth-cutover-all-paths-01KYH654/spec.md
Summary
Close the structural cause of #2917: the runtime-state birth-cutover stamp fires only inside spec-kitty merge (merge/executor.py::_run_birth_cutover), so missions landed via GitHub-side squash/rebase arrive un-cut-over and re-red the release-gating dogfood corpus test on main. Deliver two mutually-reinforcing mechanisms, both reusing the single existing cutover authority (runtime_state_cutover.cutover_mission):
1. Auto-stamp the cutover into the mission branch at the terminal accept seam (runtime state is final; committed pre-PR so it rides through any merge). 2. A fail-closed, diff-scoped pre-merge CI guard — the binding backstop — that evaluates every mission whose corpus appears in the PR diff via the acceptance test's event-log-evidence predicate (not bare verify_backfill), is wired to fire on corpus-only PRs, and is a required, non-skippable check.
Plus a spec-kitty doctor cutover on-demand audit (FR-007). The mechanical re-green of the pre-existing backlog already shipped as hotfix #2968; this mission removes the recurrence and makes it enforceable.
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: existing specify_cli internals — migration/runtime_state_cutover.py, migration/backfill_runtime_state.py, merge/executor.py, cli/commands/accept.py, cli/commands/doctor.py; GitHub Actions CI (.github/workflows/) Storage: committed corpus artifacts under kitty-specs/<mission>/ — meta.json (PRIMARY partition, status_phase) and status.events.jsonl (COORD partition, seed events) Testing: pytest; red-first per DIRECTIVE_041; a GitHub-squash-merge simulation for US1; reuse of the acceptance test's event-log eligibility body Target Platform: Linux CI + local dev Project Type: single (CLI tool + CI workflows) Performance Goals: diff-scoped guard < 30s in CI (NFR-002); full-corpus acceptance lock remains the slower gate (NFR-001) Constraints: no direct push to origin/main (C-001); Phase-2 status_phase="1" only (C-002); do not rewrite the ~319 migrated missions (C-003); no spec-kitty execution assumed at GitHub-merge time (C-004) Scale/Scope: ~322-mission corpus; two landing paths; one CI change + two CLI seams + one audit subcommand
Charter Check
GATE: passes. Re-check after design.
reuses cutover_mission; the guard reuses the acceptance test's eligibility body. No forked writer, no parallel checker — FR-005/FR-009.
acceptance/regression test first (GitHub-squash simulation, native-mission vacuity, payload determinism, corpus-only-PR guard trigger).
PRIMARY; reuse cutover_mission's two-leg spine; coordinate with deferred #2923 (route _flip_phase through the placement port) rather than re-solving.
twice on #2917); "cut over" is proven from event-log evidence, never a vacuous verify_backfill.ok or a bare status_phase flip.
- Single canonical authority (charter + #2160 thesis): the new stamp caller
- ATDD-first / red-first: every unit of work lands its failing
- Coord/primary partition discipline: seed events → COORD,
meta.json→ - No green-washing: the acceptance test predicate is not relaxed (rejected
Project Structure
Documentation (this mission)
kitty-specs/runtime-state-birth-cutover-all-paths-01KYH654/
├── plan.md # this file
├── research.md # pre-planning grounding synthesis
├── data-model.md # "cut over" state definition
├── contracts/ # stamp-seam + guard behavioral contracts
└── tasks.md # /spec-kitty.tasks output
Source Code (repository root)
src/specify_cli/migration/runtime_state_cutover.py # cutover_mission:234, _flip_phase:186, cutover_repo:315 (single authority — REUSE)
src/specify_cli/migration/backfill_runtime_state.py # verify_backfill:888, anchor resolution:432-483 (pin leg — R5)
src/specify_cli/merge/executor.py # _run_birth_cutover:939 (reference wiring to mirror)
src/specify_cli/cli/commands/accept.py # terminal stamp seam (_commit_residual_acceptance_artifacts)
src/specify_cli/cli/commands/doctor.py # + `doctor cutover` shell → _cutover_doctor.py
src/specify_cli/cli/commands/<guard> # diff-scoped guard entrypoint (reuses eligibility body)
tests/specify_cli/migration/test_dogfood_corpus_backfilled.py # _mission_carries_event_log_runtime:142, _assert_birth_invariant_holds:242 (REUSE predicate)
.github/workflows/ # host guard; add kitty-specs/** to on.pull_request.paths (R1)
Structure Decision: single-project CLI. No new top-level packages; the work adds one CLI seam (accept-time stamp), one guard entrypoint, one doctor subcommand module, and one CI job — all reusing existing cutover internals.
Implementation Concern Map
> Concerns, not work packages. /spec-kitty.tasks translates these into WPs.
IC-01 — Terminal-lifecycle auto-stamp (single authority)
seam so the committed corpus is already cut over before the branch can land by any path.
- Purpose: Stamp the cutover into the mission branch at the terminal
accept - Relevant requirements: FR-001, FR-004, FR-005, FR-006.
- Affected surfaces:
cli/commands/accept.py,migration/runtime_state_cutover.py(reusecutover_mission), mirror ofmerge/executor.py::_run_birth_cutover. - Sequencing/depends-on: IC-02 (needs the pinned anchor to avoid divergent payloads).
- Risks: R4 —
_flip_phasewrites but does not commit; must commit both partitions into the landing branch with no reliance on the status daemon; worktree.git-redirect (WP09/#2920) means write to the branch's ownmeta.json, resume-heal, post-target-safe. Fail closed on absentmission_id(R6).
IC-02 — Pin claim-anchor resolution to one canonical leg
byte-identical across runs, machines, and landing-path contexts.
- Purpose: Make the seed payload (including the resolved claim anchor)
- Relevant requirements: NFR-004.
- Affected surfaces:
migration/backfill_runtime_state.py(anchor resolution 432-483). - Sequencing/depends-on: none (prerequisite for IC-01).
- Risks: R5 — divergent anchors leave a flipped-but-unverifiable corpus (
status_phase=1whileverify_backfillreds). Must test payload-level, not just seed-id, determinism.
IC-03 — Diff-scoped fail-closed guard (logic)
the corpus, evaluating every mission in the PR diff via event-log evidence.
- Purpose: Reject any change set that would leave an un-cut-over mission in
- Relevant requirements: FR-002, FR-003, FR-009, NFR-002, NFR-003.
- Affected surfaces: new guard entrypoint under
cli/commands/; reuses_mission_carries_event_log_runtime/_assert_birth_invariant_holds+verify_backfill. - Sequencing/depends-on: none.
- Risks: R2 — must NOT key on bare
verify_backfill.ok(vacuous for native missions); R3 — guard is the binding backstop for seam-bypass paths, so it must scan all diff-touched missions and print the exact remedy (spec-kitty migrate backfill-runtime-state --mission <slug>). Fail closed on verify error / ambiguity / absentmission_id.
IC-04 — CI wiring (guard fires on corpus-only PRs, required + non-skippable)
kitty-specs/** and cannot be silently skipped.
- Purpose: Ensure the guard actually runs on a PR whose diff is only
- Relevant requirements: FR-008.
- Affected surfaces:
.github/workflows/(host job'son.pull_request.paths; branch-protection required-checks). - Sequencing/depends-on: IC-03.
- Risks: R1 (critical) — today
kitty-specs/triggers no test workflow and the dogfood test is push-only; the guard host must addkitty-specs/(or use a no-paths workflow), run onpull_request, sit outside the srcchangesfilter, and be a required check that executes (a skipped required check passes silently). Verify live with a scratch corpus-only PR — do not trust path-filter reading (#2968 lesson).
IC-05 — On-demand cut-over audit
- Purpose: Let an operator list every mission's cut-over status outside CI.
- Relevant requirements: FR-007.
- Affected surfaces:
cli/commands/doctor.py+ new_cutover_doctor.py; backed bycutover_repo(dry_run=True). - Sequencing/depends-on: none.
- Risks: keep it a thin shell per the doctor per-subcommand convention; do not shoehorn a corpus-wide audit into the per-feature
run_doctoraggregation.
IC-06 — Durable acceptance lock + regression suite
each invariant.
- Purpose: Keep the full-corpus lock green and add the regressions that prove
- Relevant requirements: NFR-001, SC-001..SC-004.
- Affected surfaces:
tests/specify_cli/migration/test_dogfood_corpus_backfilled.pyand new tests undertests/specify_cli/. - Sequencing/depends-on: spans IC-01..IC-04 (red-first tests land within their owning WP).
- Risks: R7 — touched-only guard vs full-corpus green is resolved (#2968 cleared the backlog whole-corpus); measure the guard's 30s bound on the real corpus. Coverage: GitHub-squash simulation (US1), guard-reds-on-native-un-cut-over (US2/R2), payload-determinism-across-legs (R5), guard-fires-on-corpus-only-PR (R1).
Key Risks (pre-planning adversarial squad)
dogfood test is push-only. IC-04 must fix the trigger and verify it live.
the guard must key on event-log evidence (IC-03/FR-009).
landing branch, no status-daemon reliance.
pin the anchor leg (IC-02).
- R1 (critical) — CI topology: corpus-only PRs trigger no test workflow; the
- R2 (critical) —
verify_backfillis vacuous for natively-born missions; - R3 — seam-bypass paths make the guard, not the stamp, the binding enforcer.
- R4 — stamp writes but does not commit; commit both partitions into the
- R5 — double-stamp payload divergence → flipped-but-unverifiable corpus;
- R6 — fail closed on absent
mission_id; never slug-namespace seeds. - R7 — resolved via #2968's whole-corpus re-green; measure the 30s bound.
Complexity Tracking
No Charter Check violations. The mission adds no new architecture; it adds a second caller of an existing authority, a guard reusing an existing predicate, a thin doctor subcommand, and a CI trigger fix.
Out of Scope
Read-side placement (#2922), write-target consolidation (#2966 B/C), terminology migration (#2964), and redoing the #2968 mechanical re-green (C-005).