import Sal.ConditionedMRDTs.Metatheory.GC_Safety

/-!
# Bounded-state commit GC: the clock representation swap

`GC_Safety.lean`'s `gc_safety` prunes the **payload** (the `ver` states and event sets)
while keeping the DAG **skeleton** (`parents`) verbatim. The skeleton is a total function
`Version → List Version` over allocated ids, so the pruned store still costs
`O(number of commits ever)`: `gc_safety` reclaims the payload but not the history.

The skeleton was kept for one reason (`GC_Safety` §2): the store field `lca_events`
quantifies over *all* structural LCA triples, and restricting `parents` can mint new LCA
triples (removing common ancestors weakens the domination clause) whose event sets
falsify that invariant, even though no reachable `Step3` ever queries such a pair. Two
kept interior nodes above different frontier MCAs can have all their common ancestors
dropped.

**This file removes the skeleton** by a representation swap: the
commit graph is a delta-compression of event sets, so a version's ancestry role is
carried by its event set used as a **clock**. Ancestry becomes `⊆` (pointwise `≤` on the
per-event clock), and the LCA of a head pair is the kept version at the **clock meet**
`E(v₁) ∩ E(v₂)` (this is exactly `lca_events`). The bounded store keeps only the kept
payloads and drops `parents` to the constant empty function: `lca_events` is then
discharged trivially (`Reaches (fun _ => []) = (·=·)`, so the only structural LCA is the
reflexive one), and there is no history to store.

**The lineage-collision constraint.**
Identifying the LCA by clock *alone* is sound before datatype-level compaction (the
payload is then a function of the event set) and UNSOUND after: a same-clock version from
an unrelated lineage, used as the merge's `L`, resurrects a dropped instance. The
framework's structural `IsLCA` sidesteps this because it resolves within shared ancestry.
A clock swap cannot, so it must **restrict LCA lookup to `R_S`-compatible payloads**. The
guard `ClockCoherent` ("two kept versions with the same
event-set clock carry the same state", i.e. the payload is a function of the clock on the
keep set) is precisely `R_S`-compatibility, and it is exactly what compaction threatens.
`clockLCA_sound` needs it; `spot_lineage_resurrection` witnesses the resurrection when it
is dropped.

**What is proved here.**
* `clockPrune` (§1): the bounded store, a genuine `Configuration` with `parents ≡ []` and
  `ver` supported on the keep set. `clockPrune_support` bounds the state at `O(keep-set)`.
* `ClockLCAState` (§3): the clock LCA lookup, over event sets only.
  `clockLCA_complete` shows every head pair with a structural LCA has its LCA recovered by
  the clock lookup in the bounded store (the meet is `lca_events`, and the LCA is kept by
  `lca_not_dropped`), so the swap never disables a merge the skeleton enabled.
* `clockLCA_sound` (§4): under `ClockCoherent`, every clock LCA carries the structural
  LCA's state, so the clock merge computes the same result as the structural merge.
* `Step3C` / `gc_safety_bounded` (§5): the bounded LTS runs on the clock store, every head
  run and head read is preserved, and state is bounded (no skeleton).
* §6: the machine-checked SPOT (PASS: a bounded prune whose future head-pair merge still
  finds its LCA at the clock meet and reads correctly; FAIL: the lineage resurrection).

Interaction with virtual LCAs (`gc_safetyV`, `mcasClosure`): the clock swap resolves the
**gated** merge (a single LCA = the clock meet). A **virtual** merge over a criss-cross
antichain of MCAs is a genuine DAG-order notion (the meet is not a single clock), so its
bounded-state form needs the retained lineage among the MCA closure and is left owed; see
the closing note.
-/

namespace Sal.ConditionedMRDTs

open Sal.Emulation

section BoundedGC

variable {D : ConditionedMRDTSig}

open Classical

/-! ## §0  Reachability over the empty skeleton -/

/-- With no edges, reachability collapses to equality: this is what makes the trivial
`lca_events` of the skeleton-free store hold (the only structural LCA is the reflexive
one). -/
theorem reaches_empty_eq {a b : Version}
    (h : Reaches (fun _ => ([] : List Version)) a b) : a = b := by
  induction h with
  | refl => rfl
  | tail _ hstep _ => exact absurd hstep List.not_mem_nil

/-! ## §1  The bounded store `clockPrune`

The clock store is `dropVer`'s payload prune (states/event sets outside `Keep'` removed)
with the DAG skeleton flattened to `parents ≡ []`. It is a genuine `Configuration`: the
only fields sensitive to `parents` are `parents_lt` (vacuous) and `lca_events`
(`reaches_empty_eq` collapses `IsLCA` to the reflexive triple, where the intersection is
`Set.inter_self`). Replica reads (`N`, `L`, `vis`) and the kept payloads are carried
verbatim from `C`. -/
open Classical in
/-- The bounded (skeleton-free) prune: `dropVer`'s payload prune with the DAG skeleton
flattened to the constant empty function. Parametrized by the drop set `S` (exactly like
`dropVer`) so the run-level simulation can prune every config along a run against the same
prune-time drop set. -/
noncomputable def flatDropVer (C : Configuration D) (S : Set Version)
    (h0 : 0 ∉ S)
    (hheads : ∀ r v, C.head r = some v → (C.ver v).isSome → v ∉ S) :
    Configuration D :=
  { dropVer C S h0 hheads with
    parents := fun _ => []
    parents_lt := by intro v p hp; exact absurd hp List.not_mem_nil
    lca_events := by
      intro v₁ v₂ vT s₁ e₁ s₂ e₂ sT eT hlca h1 h2 hT
      have hv1 : vT = v₁ := reaches_empty_eq hlca.1
      have hv2 : vT = v₂ := reaches_empty_eq hlca.2.1
      subst hv1; subst hv2
      rw [h1, Option.some.injEq, Prod.mk.injEq] at h2 hT
      obtain ⟨_, rfl⟩ := h2
      obtain ⟨_, rfl⟩ := hT
      exact (Set.inter_self e₁).symm }

@[simp] theorem flatDropVer_ver (C : Configuration D) (S : Set Version) (h0 hheads)
    (v : Version) :
    (flatDropVer C S h0 hheads).ver v = if v ∈ S then none else C.ver v := rfl

@[simp] theorem flatDropVer_parents (C : Configuration D) (S : Set Version) (h0 hheads)
    (v : Version) : (flatDropVer C S h0 hheads).parents v = [] := rfl

@[simp] theorem flatDropVer_head (C : Configuration D) (S : Set Version) (h0 hheads) :
    (flatDropVer C S h0 hheads).head = C.head := rfl

@[simp] theorem flatDropVer_N (C : Configuration D) (S : Set Version) (h0 hheads) :
    (flatDropVer C S h0 hheads).N = C.N := rfl

/-- The bounded (skeleton-free) prune of `C` at its own prune point, over `Keep'`. -/
noncomputable def clockPrune (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) : Configuration D :=
  flatDropVer C (droppedSet C) (zero_not_dropped C) (heads_kept (gcInv_init C hStore))

@[simp] theorem clockPrune_ver (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) (v : Version) :
    (clockPrune C hStore).ver v
      = if v ∈ droppedSet C then none else C.ver v := rfl

@[simp] theorem clockPrune_parents (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) (v : Version) :
    (clockPrune C hStore).parents v = [] := rfl

@[simp] theorem clockPrune_head (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) :
    (clockPrune C hStore).head = C.head := rfl

@[simp] theorem clockPrune_N (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) :
    (clockPrune C hStore).N = C.N := rfl

/-! ### The state bound: `O(keep-set)` -/

/-- **The state bound.** The bounded store carries a payload only at kept versions: its
`ver` support is contained in `Keep'`. Combined with `clockPrune_parents` (the skeleton is
the constant empty function, `O(1)`), the entire store costs `O(|Keep'|)`, not
`O(number of commits ever)`. -/
theorem clockPrune_support (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) {v : Version}
    (hv : (clockPrune C hStore).ver v ≠ none) : v ∈ keepSet' C := by
  rw [clockPrune_ver] at hv
  by_cases hd : v ∈ droppedSet C
  · rw [if_pos hd] at hv; exact absurd rfl hv
  · rw [if_neg hd] at hv
    by_contra hk
    exact hd ⟨Option.isSome_iff_ne_none.mpr hv, hk⟩

/-- The skeleton is gone: the bounded store's parent function is constantly empty, so no
per-commit history is retained. -/
theorem clockPrune_no_skeleton (C : Configuration D)
    (hStore : StoreInv C.ver C.parents) :
    (clockPrune C hStore).parents = fun _ => [] := rfl

/-! ## §3  The clock LCA lookup, and completeness

The merge's LCA state, read structurally as `ver vT` off the DAG (`IsLCA parents`), is
recovered from event sets alone: the LCA's clock is the pointwise **meet**
`E(v₁) ∩ E(v₂)` (this is `lca_events`). The lookup below reads only event sets, never
`parents`. -/

/-- `u` is a **clock LCA** of the head pair `(v₁, v₂)` carrying state `su`: its event-set
clock is the pointwise meet `E(v₁) ∩ E(v₂)` of the heads' clocks. Reads event sets only,
never `parents`. -/
def ClockLCA (cc : Configuration D) (v₁ v₂ u : Version) (su : D.State) : Prop :=
  ∃ s₁ e₁ s₂ e₂, cc.ver v₁ = some (s₁, e₁) ∧ cc.ver v₂ = some (s₂, e₂) ∧
    cc.ver u = some (su, e₁ ∩ e₂)

/-- **Completeness of the swap.** Every head pair with a *structural* LCA `vT` (the
premise `Step3.merge` supplies) has, in the bounded store, `vT` as a *clock* LCA carrying
the structural LCA's state `sT`. So dropping the skeleton never disables a merge the DAG
enabled: `lca_events` gives the meet, `lca_not_dropped` keeps `vT`, `heads_kept` keeps the
heads. -/
theorem clockLCA_complete (C : Configuration D) (hStore : StoreInv C.ver C.parents)
    {r₁ r₂ : Replica} {v₁ v₂ vT : Version} {s₁ s₂ sT : D.State}
    {ev₁ ev₂ evT : Set (Op D.AppOp)}
    (h_head₁ : C.head r₁ = some v₁) (h_head₂ : C.head r₂ = some v₂)
    (h_ver₁ : C.ver v₁ = some (s₁, ev₁)) (h_ver₂ : C.ver v₂ = some (s₂, ev₂))
    (h_lca : IsLCA C.parents v₁ v₂ vT) (h_verT : C.ver vT = some (sT, evT)) :
    ClockLCA (clockPrune C hStore) v₁ v₂ vT sT := by
  have hInv := gcInv_init C hStore
  have hv1 : v₁ ∉ droppedSet C := heads_kept hInv r₁ v₁ h_head₁ (by rw [h_ver₁]; rfl)
  have hv2 : v₂ ∉ droppedSet C := heads_kept hInv r₂ v₂ h_head₂ (by rw [h_ver₂]; rfl)
  have hvT : vT ∉ droppedSet C :=
    lca_not_dropped hInv h_head₁ h_head₂ h_ver₁ h_ver₂ h_lca h_verT
  have hmeet : evT = ev₁ ∩ ev₂ := C.lca_events h_lca h_ver₁ h_ver₂ h_verT
  refine ⟨s₁, ev₁, s₂, ev₂, ?_, ?_, ?_⟩
  · rw [clockPrune_ver, if_neg hv1]; exact h_ver₁
  · rw [clockPrune_ver, if_neg hv2]; exact h_ver₂
  · rw [clockPrune_ver, if_neg hvT, h_verT, hmeet]

/-! ## §4  The lineage guard: `R_S`-compatible payloads

Clock identification alone is unsound once datatype-level compaction exists: a same-clock
version from an unrelated lineage, used as `L`,
resurrects a dropped instance. The framework's structural `IsLCA` sidesteps this by
resolving within shared ancestry; the clock swap cannot, so it **restricts LCA lookup to
`R_S`-compatible payloads**, formalized as `ClockCoherent`. -/

/-- **The lineage guard** (`R_S`-compatibility): two kept versions with the same event-set
clock carry the same state, i.e. the payload is a function of the clock on the store.
Automatic before compaction (states are determined by their event sets); compaction is
precisely what breaks it (`spot_lineage_resurrection`). -/
def ClockCoherent (cc : Configuration D) : Prop :=
  ∀ u u' su su' e, cc.ver u = some (su, e) → cc.ver u' = some (su', e) → su = su'

/-- **Clock LCA is well-defined under the guard.** Any two clock-LCA candidates of a head
pair carry the same state: the heads pin the meet clock `E(v₁) ∩ E(v₂)`, so both share
that clock and `ClockCoherent` forces the states equal. The lookup therefore returns a
unique state regardless of which same-clock version it lands on. -/
theorem clockLCA_sound {cc : Configuration D} (hCoh : ClockCoherent cc)
    {v₁ v₂ u u' : Version} {su su' : D.State}
    (hu : ClockLCA cc v₁ v₂ u su) (hu' : ClockLCA cc v₁ v₂ u' su') : su = su' := by
  obtain ⟨s₁, e₁, s₂, e₂, hv1, hv2, hru⟩ := hu
  obtain ⟨s₁', e₁', s₂', e₂', hv1', hv2', hru'⟩ := hu'
  rw [hv1, Option.some.injEq, Prod.mk.injEq] at hv1'
  rw [hv2, Option.some.injEq, Prod.mk.injEq] at hv2'
  obtain ⟨_, rfl⟩ := hv1'
  obtain ⟨_, rfl⟩ := hv2'
  exact hCoh u u' su su' (e₁ ∩ e₂) hru hru'

/-- **The swap recovers the structural LCA state.** Under the guard, any clock LCA of a
head pair in the bounded store carries exactly the structural LCA's state: the clock meet,
resolved on `R_S`-compatible payloads, reconstructs what the dropped skeleton's `IsLCA`
would have read. This is the merge-agreement content of `gc_safety_bounded`. -/
theorem clockLCA_recovers (C : Configuration D) (hStore : StoreInv C.ver C.parents)
    (hCoh : ClockCoherent (clockPrune C hStore))
    {r₁ r₂ : Replica} {v₁ v₂ vT u : Version} {s₁ s₂ sT su : D.State}
    {ev₁ ev₂ evT : Set (Op D.AppOp)}
    (h_head₁ : C.head r₁ = some v₁) (h_head₂ : C.head r₂ = some v₂)
    (h_ver₁ : C.ver v₁ = some (s₁, ev₁)) (h_ver₂ : C.ver v₂ = some (s₂, ev₂))
    (h_lca : IsLCA C.parents v₁ v₂ vT) (h_verT : C.ver vT = some (sT, evT))
    (hu : ClockLCA (clockPrune C hStore) v₁ v₂ u su) : su = sT :=
  clockLCA_sound hCoh hu
    (clockLCA_complete C hStore h_head₁ h_head₂ h_ver₁ h_ver₂ h_lca h_verT)

/-! ## §5  The bounded LTS `Step3C` and the run simulation

`Step3C` is `Step3` on the bounded store: every rule **freezes `parents`** (the skeleton
never grows, so the bounded store stays skeleton-free along a run), and Merge reads its LCA
state by the **clock lookup** (`ClockLCA`) rather than the structural `IsLCA` and stored
`ver vT`. Every other premise is verbatim `Step3`. -/

/-- The ternary step relation on the bounded store: `parents` frozen, Merge clock-gated. -/
inductive Step3C (D : ConditionedMRDTSig) :
    Configuration D → Label3 D → Configuration D → Prop where
  | createReplica {C : Configuration D} {r : Replica}
      (h_fresh : C.N r = none)
      (C' : Configuration D)
      (hN : C'.N = updateRep C.N r D.init)
      (hL : C'.L = updateRep C.L r ∅)
      (hvis : C'.vis = C.vis)
      (hver : C'.ver = C.ver)
      (hhead : C'.head = fun r' => if r' = r then some 0 else C.head r')
      (hparents : C'.parents = C.parents) :
      Step3C D C (.createReplica r) C'
  | apply {C : Configuration D} {t : Timestamp} {r : Replica} {o : D.AppOp}
      {v : Version} {s : D.State} {ev : Set (Op D.AppOp)} {vnew : Version}
      (h_head : C.head r = some v)
      (h_ver : C.ver v = some (s, ev))
      (h_fresh_t : ∀ e', e' ∈ C.events → Op.time e' ≠ t)
      (h_fresh_store : ∀ w sw Ew, C.ver w = some (sw, Ew) →
        ∀ e' ∈ Ew, Op.time e' ≠ t)
      (h_vnew : C.ver vnew = none)
      (h_rank : v < vnew)
      (C' : Configuration D)
      (hN : C'.N = updateRep C.N r (D.update s (t, r, o)))
      (hL : C'.L = updateRep C.L r (ev ∪ {(t, r, o)}))
      (hvis : C'.vis = fun a b => C.vis a b ∨ (ev a ∧ b = (t, r, o)))
      (hver : C'.ver = fun w => if w = vnew
        then some (D.update s (t, r, o), ev ∪ {(t, r, o)}) else C.ver w)
      (hhead : C'.head = fun r' => if r' = r then some vnew else C.head r')
      (hparents : C'.parents = C.parents) :
      Step3C D C (.apply t r o) C'
  /-- **Merge, clock-gated**: the LCA state `sT` is read by the clock lookup `ClockLCA`
  (event-set meet), not from the (dropped) skeleton. -/
  | merge {C : Configuration D} {r₁ r₂ : Replica}
      {v₁ v₂ vT vm : Version} {s₁ s₂ sT : D.State}
      {ev₁ ev₂ : Set (Op D.AppOp)}
      (h_head₁ : C.head r₁ = some v₁) (h_head₂ : C.head r₂ = some v₂)
      (h_ver₁ : C.ver v₁ = some (s₁, ev₁)) (h_ver₂ : C.ver v₂ = some (s₂, ev₂))
      (h_clca : ClockLCA C v₁ v₂ vT sT)
      (h_vm : C.ver vm = none)
      (h_rank₁ : v₁ < vm) (h_rank₂ : v₂ < vm)
      (C' : Configuration D)
      (hN : C'.N = updateRep C.N r₁ (D.mergeL sT s₁ s₂))
      (hL : C'.L = updateRep C.L r₁ (ev₁ ∪ ev₂))
      (hvis : C'.vis = C.vis)
      (hver : C'.ver = fun w => if w = vm
        then some (D.mergeL sT s₁ s₂, ev₁ ∪ ev₂) else C.ver w)
      (hhead : C'.head = fun r' => if r' = r₁ then some vm else C.head r')
      (hparents : C'.parents = C.parents) :
      Step3C D C (.merge r₁ r₂) C'
  | query {C : Configuration D} {r : Replica} {q : D.Query}
      {v : D.Value} {s : D.State}
      (h_s : C.N r = some s)
      (h_val : v = D.query s q) :
      Step3C D C (.query r q v) C

/-- The bounded ternary LTS. -/
def labeledTS3C (D : ConditionedMRDTSig) : LabeledTS where
  State := Configuration D
  Label := Label3 D
  step := Step3C D
  silent := fun _ => False

/-- **Single-step simulation.** Every `Step3` step on the skeleton is matched, same label,
by a `Step3C` step on the bounded (`flatDropVer`) images. Non-merge cases are `step_dropVer`
verbatim over the flattened store; Merge's structural `IsLCA`/`ver vT` read is replaced by
the clock lookup (`clockLCA_complete`'s construction at the fixed prune point), and every
rule's frozen `parents` is `rfl` (both sides are the empty skeleton). -/
theorem step_clockFlat {C₀ C C' : Configuration D} {ℓ : Label3 D}
    (hInv : GCInv C₀ C) (hstep : Step3 D C ℓ C')
    (hh' : ∀ r v, C'.head r = some v → (C'.ver v).isSome → v ∉ droppedSet C₀) :
    Step3C D (flatDropVer C (droppedSet C₀) (zero_not_dropped C₀) (heads_kept hInv)) ℓ
      (flatDropVer C' (droppedSet C₀) (zero_not_dropped C₀) hh') := by
  cases hstep with
  | @createReplica r h_fresh C' hN hL hvis hver hhead hparents =>
    refine Step3C.createReplica ?_ _ ?_ ?_ ?_ ?_ ?_ ?_
    · exact h_fresh
    · exact hN
    · exact hL
    · exact hvis
    · funext w; simp only [flatDropVer_ver, hver]
    · exact hhead
    · rfl
  | @apply t r o v s ev vnew h_head h_ver h_fresh_t h_fresh_store h_vnew h_rank
      C' hN hL hvis hver hhead hparents =>
    have hvKeep : v ∉ droppedSet C₀ :=
      heads_kept hInv r v h_head (by rw [h_ver]; rfl)
    have hvnewKeep : vnew ∉ droppedSet C₀ := fun hm =>
      not_alloc₀_of_fresh hInv h_vnew hm.1
    refine Step3C.apply (v := v) (s := s) (ev := ev) (vnew := vnew)
      ?_ ?_ ?_ ?_ ?_ h_rank _ ?_ ?_ ?_ ?_ ?_ ?_
    · exact h_head
    · show (if v ∈ droppedSet C₀ then none else C.ver v) = some (s, ev)
      rw [if_neg hvKeep]; exact h_ver
    · exact h_fresh_t
    · intro w sw Ew hw
      by_cases hwS : w ∈ droppedSet C₀
      · rw [flatDropVer_ver, if_pos hwS] at hw; cases hw
      · rw [flatDropVer_ver, if_neg hwS] at hw
        exact h_fresh_store w sw Ew hw
    · show (if vnew ∈ droppedSet C₀ then none else C.ver vnew) = none
      rw [if_neg hvnewKeep]; exact h_vnew
    · exact hN
    · exact hL
    · exact hvis
    · funext w
      simp only [flatDropVer_ver, hver]
      by_cases hw : w = vnew
      · subst hw; simp [hvnewKeep]
      · simp [hw]
    · exact hhead
    · rfl
  | @merge r₁ r₂ v₁ v₂ vT vm s₁ s₂ sT ev₁ ev₂ evT h_head₁ h_head₂ h_ver₁ h_ver₂
      h_lca h_verT h_vm h_rank₁ h_rank₂ C' hN hL hvis hver hhead hparents =>
    have hv₁Keep : v₁ ∉ droppedSet C₀ :=
      heads_kept hInv r₁ v₁ h_head₁ (by rw [h_ver₁]; rfl)
    have hv₂Keep : v₂ ∉ droppedSet C₀ :=
      heads_kept hInv r₂ v₂ h_head₂ (by rw [h_ver₂]; rfl)
    have hvTKeep : vT ∉ droppedSet C₀ :=
      lca_not_dropped hInv h_head₁ h_head₂ h_ver₁ h_ver₂ h_lca h_verT
    have hvmKeep : vm ∉ droppedSet C₀ := fun hm =>
      not_alloc₀_of_fresh hInv h_vm hm.1
    have hmeet : evT = ev₁ ∩ ev₂ := C.lca_events h_lca h_ver₁ h_ver₂ h_verT
    refine Step3C.merge (v₁ := v₁) (v₂ := v₂) (vT := vT) (vm := vm)
      (s₁ := s₁) (s₂ := s₂) (sT := sT) (ev₁ := ev₁) (ev₂ := ev₂)
      ?_ ?_ ?_ ?_ ?_ ?_ h_rank₁ h_rank₂ _ ?_ ?_ ?_ ?_ ?_ ?_
    · exact h_head₁
    · exact h_head₂
    · show (if v₁ ∈ droppedSet C₀ then none else C.ver v₁) = some (s₁, ev₁)
      rw [if_neg hv₁Keep]; exact h_ver₁
    · show (if v₂ ∈ droppedSet C₀ then none else C.ver v₂) = some (s₂, ev₂)
      rw [if_neg hv₂Keep]; exact h_ver₂
    · refine ⟨s₁, ev₁, s₂, ev₂, ?_, ?_, ?_⟩
      · show (if v₁ ∈ droppedSet C₀ then none else C.ver v₁) = some (s₁, ev₁)
        rw [if_neg hv₁Keep]; exact h_ver₁
      · show (if v₂ ∈ droppedSet C₀ then none else C.ver v₂) = some (s₂, ev₂)
        rw [if_neg hv₂Keep]; exact h_ver₂
      · show (if vT ∈ droppedSet C₀ then none else C.ver vT) = some (sT, ev₁ ∩ ev₂)
        rw [if_neg hvTKeep, h_verT, hmeet]
    · show (if vm ∈ droppedSet C₀ then none else C.ver vm) = none
      rw [if_neg hvmKeep]; exact h_vm
    · exact hN
    · exact hL
    · exact hvis
    · funext w
      simp only [flatDropVer_ver, hver]
      by_cases hw : w = vm
      · subst hw; simp [hvmKeep]
      · simp [hw]
    · exact hhead
    · rfl
  | @query r q v s h_s h_val =>
    refine Step3C.query (s := s) ?_ ?_
    · exact h_s
    · exact h_val

/-- A labelled `Step3C` run (finite trace on the bounded LTS). -/
inductive StepsC (D : ConditionedMRDTSig) :
    Configuration D → List (Label3 D) → Configuration D → Prop where
  | nil (C : Configuration D) : StepsC D C [] C
  | cons {C C' C'' : Configuration D} {ℓ : Label3 D} {ℓs : List (Label3 D)} :
      Step3C D C ℓ C' → StepsC D C' ℓs C'' → StepsC D C (ℓ :: ℓs) C''

/-- **Run-level simulation.** The whole `Step3` trace transports, label for label, to a
`Step3C` trace on the bounded images. -/
theorem steps_clockFlat {C₀ : Configuration D} {C ℓs C'}
    (hrun : Steps D C ℓs C') (hInv : GCInv C₀ C)
    (hh : ∀ r v, C.head r = some v → (C.ver v).isSome → v ∉ droppedSet C₀)
    (hh' : ∀ r v, C'.head r = some v → (C'.ver v).isSome → v ∉ droppedSet C₀) :
    StepsC D (flatDropVer C (droppedSet C₀) (zero_not_dropped C₀) hh) ℓs
      (flatDropVer C' (droppedSet C₀) (zero_not_dropped C₀) hh') := by
  induction hrun with
  | nil => exact StepsC.nil _
  | @cons Ca Cb Cc ℓ ℓs hstep hrest ih =>
    exact StepsC.cons
      (step_clockFlat hInv hstep (heads_kept (gcInv_step hInv hstep)))
      (ih (gcInv_step hInv hstep) (heads_kept (gcInv_step hInv hstep)) hh')

/-! ## §5.1  The main theorem `gc_safety_bounded` -/

/-- **Bounded-state commit GC.** Pruning to the clock store
`clockPrune C₀` (the keep-set payloads, DAG skeleton dropped) over the keep set `Keep'`:

1. **preserves every head run**: the entire `Step3` trace replays, label for label, on the
   bounded LTS `Step3C` (Merge resolves its LCA state by the clock meet, `clockLCA_complete`,
   not by the dropped skeleton);
2. **preserves every head read**: the transported run's `N` agrees with `C`'s at every
   replica, at every point (definitional, `flatDropVer_N`);
3. **bounds state at `O(|Keep'|)`**: the clock store's `ver` support is contained in
   `Keep'` (`clockPrune_support`) and its skeleton is the constant empty function
   (`clockPrune_parents`), so it costs `O(number of live commits)`, not
   `O(number of commits ever)`.

The single hypothesis is `StoreInv C₀` (supplied by plain reachability,
`storeInv_reachable`). The merge results replayed here are the *structural* LCA states
(`step_clockFlat` witnesses each clock LCA by the true `vT`); that the bounded LTS is
moreover deterministic on the merge state, no unrelated-lineage clock collision resurrects
a dropped instance, is `clockLCA_recovers` under the `ClockCoherent` (`R_S`-compatible)
guard, with `spot_lineage_resurrection` the countermodel when the guard is dropped. -/
theorem gc_safety_bounded (C₀ : Configuration D) (hStore : StoreInv C₀.ver C₀.parents)
    {ℓs : List (Label3 D)} {C : Configuration D} (hRun : Steps D C₀ ℓs C) :
    StepsC D (clockPrune C₀ hStore) ℓs
      (flatDropVer C (droppedSet C₀) (zero_not_dropped C₀)
        (heads_kept (gcInv_steps hRun (gcInv_init C₀ hStore))))
    ∧ (∀ r, (flatDropVer C (droppedSet C₀) (zero_not_dropped C₀)
        (heads_kept (gcInv_steps hRun (gcInv_init C₀ hStore)))).N r = C.N r)
    ∧ (∀ v, (clockPrune C₀ hStore).ver v ≠ none → v ∈ keepSet' C₀)
    ∧ (∀ v, (clockPrune C₀ hStore).parents v = []) := by
  refine ⟨?_, ?_, ?_, ?_⟩
  · exact steps_clockFlat hRun (gcInv_init C₀ hStore)
      (heads_kept (gcInv_init C₀ hStore)) _
  · intro _; rfl
  · intro v hv; exact clockPrune_support C₀ hStore hv
  · intro v; rfl

#print axioms gc_safety_bounded

end BoundedGC

/-! ## §6  SPOT: the clock swap and the lineage-collision countermodel

Two concrete verdicts over `GSetCond`, both hand-derived (never `#eval`'d).

**PASS** reuses `GCSpot.Cex` (the §7 store of `GC_Safety`, pre-compaction, so the payload
is a function of the event set): the future head-pair merge of heads `4, 5` finds its LCA
at the clock meet `E(4) ∩ E(5) = {e1,e2} = E(2)`, and the bounded store still carries
`(S2, {e1,e2})` at version `2`, so the merge reads the correct LCA state with no skeleton.

**FAIL** (the lineage resurrection): a *compacted* store
`Ccoll` where two versions share the event-set clock `{a1,…}` but carry different states
(`{10}` on the kept lineage, `∅` on the compacted lineage). The clock lookup for the head
pair `(3,4)`, whose meet clock is exactly that shared clock, returns *both* states, so
clock identification alone resurrects the dropped `10` (or drops it) depending on the
lineage it lands on. This is why the swap must restrict to `R_S`-compatible payloads:
`ClockCoherent Ccoll` is provably false. -/

namespace BoundedSpot

open GCSpot

/-! ### PASS: the clock meet recovers the structural LCA on the coherent store -/

/-- Hand-derived: `E(4) ∩ E(5) = F2` (both `⊆` directions). Pins the meet away from the
degenerate `∅` (`spot_meet_nonempty`). -/
theorem spot_meet_eq : F4 ∩ F5 = F2 := by
  apply Set.Subset.antisymm F45_sub_F2
  intro x hx
  simp only [F2, Set.mem_insert_iff, Set.mem_singleton_iff] at hx
  rcases hx with rfl | rfl
  · exact ⟨Or.inl rfl, Or.inl rfl⟩
  · exact ⟨Or.inr (Or.inl rfl), Or.inr (Or.inl rfl)⟩

/-- **SPOT PASS**: in the bounded (skeleton-free) prune of `Cex`, the head pair `(4,5)`
has version `2` as its *clock* LCA at the meet `F4 ∩ F5`, carrying the hand-derived state
`S2`. The structural skeleton has been dropped; the LCA is found by event sets alone. -/
theorem spot_clock_lca_pass :
    ClockLCA (clockPrune Cex storeInvEx) 4 5 2 S2 :=
  clockLCA_complete Cex storeInvEx (r₁ := 0) (r₂ := 1)
    rfl rfl verEx4 verEx5 spot_isLCA verEx2

open Classical in
/-- **SPOT PASS, read pinned**: the bounded store still registers the LCA `(S2, F2)` at
version `2` (the merge reads it correctly), while the dropped version `1` is genuinely
absent (`spot_bounded_dropped`). -/
theorem spot_clock_lca_present :
    (clockPrune Cex storeInvEx).ver 2 = some (S2, F2) := by
  show (if (2 : Version) ∈ droppedSet Cex then none else Cex.ver 2) = some (S2, F2)
  rw [if_neg two_not_dropped]; rfl

open Classical in
/-- **SPOT FAIL-companion (bound is strict)**: the skeleton is gone (parents empty) and the
strictly-dropped version `1` carries no payload in the bounded store. -/
theorem spot_bounded_dropped :
    (clockPrune Cex storeInvEx).ver 1 = none ∧
    (clockPrune Cex storeInvEx).parents 3 = [] := by
  refine ⟨?_, rfl⟩
  show (if (1 : Version) ∈ droppedSet Cex then none else Cex.ver 1) = none
  rw [if_pos one_dropped]

/-- **SPOT FAIL-companion (non-degeneracy)**: the meet is not the degenerate empty clock,
and the recovered LCA state `S2` is neither head's state (not a projection of an input). -/
theorem spot_meet_nonempty : F4 ∩ F5 ≠ (∅ : Set O) ∧ S2 ≠ S4 ∧ S2 ≠ S5 := by
  refine ⟨?_, ?_, ?_⟩
  · intro h
    have : e1 ∈ F4 ∩ F5 := ⟨Or.inl rfl, Or.inl rfl⟩
    rw [h] at this; exact this
  · intro h
    have h30 : (30 : Nat) ∈ S4 := Or.inr (Or.inr rfl)
    rw [← h] at h30
    simp only [S2, Set.mem_insert_iff, Set.mem_singleton_iff] at h30
    rcases h30 with h | h <;> exact absurd h (by decide)
  · intro h
    have h40 : (40 : Nat) ∈ S5 := Or.inr (Or.inr rfl)
    rw [← h] at h40
    simp only [S2, Set.mem_insert_iff, Set.mem_singleton_iff] at h40
    rcases h40 with h | h <;> exact absurd h (by decide)

/-! ### FAIL: the lineage-collision resurrection on a compacted store -/

/-- Ops for the collision store (distinct timestamps). -/
abbrev O' := Op GSetCond.AppOp
def a1 : O' := (1, 0, (10 : Nat))
def a2 : O' := (2, 0, (20 : Nat))
def a3 : O' := (3, 0, (30 : Nat))

def St10 : Set Nat := {10}
def Ehead3 : Set O' := {a1, a2}
def Ehead4 : Set O' := {a1, a3}

/-- The compacted store. Version `1` (kept lineage) and version `2` (compacted lineage)
share the clock `Ehead3 ∩ Ehead4` but carry different states (`{10}` vs `∅`); versions
`3, 4` are the heads whose meet is exactly that shared clock. -/
noncomputable def verColl : Version → Option (GSetCond.State × Set O') := fun v =>
  if v = 0 then some ((∅ : Set Nat), (∅ : Set O'))
  else if v = 1 then some (St10, Ehead3 ∩ Ehead4)
  else if v = 2 then some ((∅ : Set Nat), Ehead3 ∩ Ehead4)
  else if v = 3 then some ((∅ : Set Nat), Ehead3)
  else if v = 4 then some ((∅ : Set Nat), Ehead4)
  else none

/-- The compacted configuration: an inert core (no replicas, no visibility) with the
collision store and the empty skeleton. Not reachable; it models a store *after*
datatype-level compaction, exactly where clock identification loses lineage. -/
noncomputable def Ccoll : Configuration GSetCond where
  N := fun _ => none
  L := fun _ => none
  vis := fun _ _ => False
  dom_eq := by intro r; exact iff_of_true rfl rfl
  vis_src := fun h => absurd h id
  vis_tgt := fun h => absurd h id
  vis_causal := fun h _ _ => absurd h id
  timestamps_distinct := by
    intro a b r s r' s' hL _ _ _ _
    have hL' : (none : Option (Set (Op GSetCond.AppOp))) = some s := hL
    simp at hL'
  causal_mono := fun h => absurd h id
  vis_total_same_replica := by
    intro a b r s r' s' hL _ _ _ _ _
    have hL' : (none : Option (Set (Op GSetCond.AppOp))) = some s := hL
    simp at hL'
  ver := verColl
  head := fun _ => none
  parents := fun _ => []
  parents_lt := by intro v p hp; exact absurd hp List.not_mem_nil
  ver_init := rfl
  head_coherent := by
    intro r v hr
    have hr' : (none : Option Version) = some v := hr
    simp at hr'
  ver_inv := fun _ _ _ _ => trivial
  lca_events := by
    intro v₁ v₂ vT s₁ e₁ s₂ e₂ sT eT hlca h1 h2 hT
    have hv1 : vT = v₁ := reaches_empty_eq hlca.1
    have hv2 : vT = v₂ := reaches_empty_eq hlca.2.1
    subst hv1; subst hv2
    rw [h1, Option.some.injEq, Prod.mk.injEq] at h2 hT
    obtain ⟨_, rfl⟩ := h2
    obtain ⟨_, rfl⟩ := hT
    exact (Set.inter_self e₁).symm

theorem verColl3 : Ccoll.ver 3 = some ((∅ : Set Nat), Ehead3) := rfl
theorem verColl4 : Ccoll.ver 4 = some ((∅ : Set Nat), Ehead4) := rfl
theorem verColl1 : Ccoll.ver 1 = some (St10, Ehead3 ∩ Ehead4) := rfl
theorem verColl2 : Ccoll.ver 2 = some ((∅ : Set Nat), Ehead3 ∩ Ehead4) := rfl

/-- The kept lineage's state carries `10`; the compacted one does not. -/
theorem St10_ne_empty : St10 ≠ (∅ : Set Nat) := by
  intro h
  have : (10 : Nat) ∈ St10 := rfl
  rw [h] at this; exact this

/-- **SPOT FAIL: the lineage resurrection.** For the same head pair `(3,4)` in the
compacted store, the clock lookup returns version `1` with state `{10}` AND version `2`
with state `∅`: identical clock (the meet `Ehead3 ∩ Ehead4`), divergent state. Clock
identification alone resurrects the dropped `10` (picking `1`) or loses it (picking `2`),
and the guard `ClockCoherent` is provably false. -/
theorem spot_lineage_resurrection :
    ClockLCA Ccoll 3 4 1 St10 ∧ ClockLCA Ccoll 3 4 2 (∅ : Set Nat) ∧
    St10 ≠ (∅ : Set Nat) ∧ ¬ ClockCoherent Ccoll := by
  refine ⟨⟨_, Ehead3, _, Ehead4, verColl3, verColl4, verColl1⟩,
          ⟨_, Ehead3, _, Ehead4, verColl3, verColl4, verColl2⟩,
          St10_ne_empty, ?_⟩
  intro hCoh
  exact St10_ne_empty (hCoh 1 2 St10 (∅ : Set Nat) (Ehead3 ∩ Ehead4) verColl1 verColl2)

/-- Conversely, had the guard held (`R_S`-compatible payloads), `clockLCA_sound` would have
forced the two lookups equal: the resurrection is exactly the guard's failure. -/
theorem spot_guard_would_fix
    (hCoh : ClockCoherent Ccoll) : St10 = (∅ : Set Nat) :=
  clockLCA_sound hCoh
    (⟨_, Ehead3, _, Ehead4, verColl3, verColl4, verColl1⟩ : ClockLCA Ccoll 3 4 1 St10)
    (⟨_, Ehead3, _, Ehead4, verColl3, verColl4, verColl2⟩ :
      ClockLCA Ccoll 3 4 2 (∅ : Set Nat))

/-! ### Axiom audit -/

#print axioms spot_clock_lca_pass
#print axioms spot_lineage_resurrection
#print axioms spot_guard_would_fix

end BoundedSpot

end Sal.ConditionedMRDTs
