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.paths includes kitty-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 lane mirror (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.