Contracts
pre-merge-guard.md
Contract: Fail-Closed Pre-Merge Guard
Concern: IC-03 / IC-04 · Requirements: FR-002, FR-003, FR-008, FR-009, NFR-002, NFR-003
Trigger
Every pull request whose diff touches kitty-specs/ — including a diff that touches ONLY kitty-specs/. Hosted in a pull_request-scoped CI job that is outside the src/ changes filter and registered as a required, non-skippable** status check.
Inputs
The set of missions whose corpus (meta.json / status.events.jsonl) appears in the PR diff (all of them, not just the "current" mission).
Behavior
For each diff-touched mission, decide cut over using the data-model definition — the acceptance test's event-log-evidence predicate (_mission_carries_event_log_runtime) + non-empty-snapshot birth invariant (_assert_birth_invariant_holds) + verify_backfill as a necessary-not-sufficient check. Do NOT key solely on verify_backfill.ok (vacuous for native missions).
Outcome (MUST)
mission(s) and prints the exact remedy: spec-kitty migrate backfill-runtime-state --mission <slug>.
absent mission_id — never pass on uncertainty.
- Pass: every diff-touched mission is cut over.
- Fail (non-zero): any diff-touched mission is un-cut-over — message names the
- Fail closed: on any verify error, missing artifact, ambiguous corpus, or
Performance
corpus (measure on the real corpus — the full-corpus dogfood test is separate).
- Diff-scoped (touched missions only); completes < 30s in CI on the current
Wiring guarantees (FR-008 / R1)
host has no paths filter).
changes gate.
execute and exit non-zero on failure (a skipped required check passes silently).
running and able to fail.
- The host workflow's
on.pull_request.pathsincludeskitty-specs/**(or the - The job runs on
pull_request(not push-only) and is not behind the src - Registered as a required check in branch protection; the job must actually
- Verified live: a scratch PR touching only
kitty-specs/**shows the job
Acceptance tests
reds with name + remedy; all-cut-over diff → passes.
is still flagged un-cut-over by the guard.
- US2: a diff carrying an un-cut-over (incl. natively-born) mission → guard
- R1 live check: corpus-only scratch PR triggers the guard.
- R2 vacuity: a natively-born un-cut-over mission (empty
verify_backfill)
stamp-seam.md
Contract: Terminal-Lifecycle Auto-Stamp
Concern: IC-01 / IC-02 · Requirements: FR-001, FR-004, FR-005, FR-006, NFR-003, NFR-004
Trigger
The accept step of a mission's lifecycle (all WPs approved/done → runtime state is final), running on the mission branch, before any PR/merge.
Behavior
1. Resolve the mission's PRIMARY feature_dir and COORD status_feature_dir (topology-aware, exactly as merge/executor.py::_run_birth_cutover does). 2. Assert mission_id is present. If absent → fail closed (no slug-namespaced seed fallback). 3. Call the single authority runtime_state_cutover.cutover_mission(feature_dir, status_feature_dir=..., dry_run=False). Do not fork a second writer. 4. Resolve the claim anchor from the one canonical leg (see NFR-004) so the seed payload is byte-identical to what any other caller would produce. 5. status_phase is written last, only after verify.ok (resume-heal; post-target-safe; never writes a stale meta.json under a worktree .git redirect). 6. Commit BOTH partitions into the branch that will land: meta.json on PRIMARY, seed events on COORD. No reliance on the background status daemon for the commit.
Postconditions (MUST)
definition) before the branch can be merged by any path.
identical status.events.jsonl bytes.
- The committed corpus on the mission branch is cut over (per data-model
- Idempotent: re-running is a no-op — no duplicate or divergent seed events;
- Already-cut-over or eligibility-excluded missions are untouched.
MUST NOT
- Assume any spec-kitty code runs at GitHub-merge time (C-004).
- Reintroduce the frontmatter
lanemirror (C-002). - Alter already-migrated missions (C-003).
Acceptance tests
a squash merge (no spec-kitty merge), assert the target-branch corpus is cut over with no post-merge step.
status.events.jsonl bytes.
- US1 GitHub-squash simulation: finalize a mission, stamp at accept, simulate
- R5 payload determinism: stamp twice from different leg contexts → identical
- R6 fail-closed: absent
mission_id→ non-zero, no seed written.