Data model — consolidation-claim-rollback-integrity-01M3PD1T

ConsolidationState (additive, .kittify/runtime/merge/<mission_id>/state.json)

FieldTypeDefaultWrittenRead
pre_mutation_refsdict[str, str] branch → sha{} (absent key loads as {})once, fresh run, before the first mutationrollback authority; never recaptured by --resume
post_mutation_refsdict[str, str] branch → sha{}immediately before the reconciliation gaterollback authority (CAS expected value); cleared after a full restore

Existing fields reused: pre_mutation_target_sha, pre_mutation_coord_sha/ref, pre_interrupt_lane_tips (seeds), mission_number_baked, completed_wps, reconciliation_passed_target_sha (cleared after a full restore; the latter also drives the FR-011 guard).

Invariants: pre_mutation_refs is immutable for the life of the record; its keys ⊇ keys of post_mutation_refs; a branch is restored only when current == post_mutation_refs[b] (or, absent, snapshot is-ancestor-of current).

RollbackReport (value object, consolidation/rollback.py)

  • outcome_by_branch: dict[str, BranchOutcome] where BranchOutcome = RESTORED(from, to) | ALREADY_AT_SNAPSHOT(sha) | NOT_RESTORED(observed, expected, reason)
  • refused_verified_landing: bool (FR-011)
  • fully_restored: bool (all RESTORED / ALREADY_AT_SNAPSHOT and not refused)
  • render() -> str — the only source of operator-facing rollback text.

State transitions (consolidation record)

fresh → snapshot persisted → mutating → post-mutation tips persisted → gate

  • gate PASS → reconciliation_passed_target_sha set → teardown (unchanged)
  • gate FAIL/REFUSE or projection refusal → rollback → fully_restored ? (bookkeeping cleared, record kept for --resume/--abort) : (record kept, branches named)
  • --abort → rollback → fully_restored ? clear record + teardown : keep record, exit 1