import Sal.ConditionedMRDTs.MRDT_Instances.SidedRGA.SidedRGA import Sal.ConditionedMRDTs.MRDT_Instances.EmbedRGA.EmbedRGA /-! # Sided RGA: per-policy intent theorems The capstone (`sided_embed_ra_linearizable3`) is policy-free: sides are payload to convergence. This file is the other half of "one kernel, two policies", what ordering intent each side-selection policy buys: * **§1 The all-R fragment, at the instance** (`sFold_liftOp`, `sided_allR_read_eq`): under the always-R policy the sided instance IS the one-sided embed instance, fold for fold, an UNCONDITIONAL simulation, no honesty hypotheses: the all-R sided coordinate is the one-sided coordinate under the symbol map, and the sort keys coincide definitionally (`sym = symR`), so every insert lands in the same slot. Through the one-sided instance's own theorems this chains onward: the all-R sided read = the naive text buffer (tier 3) = the published tombstoned RGA's read (the compaction theorem, standalone target). * **§2 Non-interleaving at the instance** (`sided_fold_subtree_convex`): in any fold whose inserts are chain-generated (the honesty layer's `chain_gen` shape), everything the display places strictly between two members of a subtree is itself in the subtree. A replica's run whose ops chain (each anchored at the previous, what the Fugue policy produces for forward AND backward typing) is a subtree, so no concurrent survivor can ever interleave into it. This is the datatype half of the L19 discharge as a theorem; the SPOT replays the L19 trace and watches the blocks stay contiguous. **The re-derivation decision (recorded).** The one-sided instance file stays as-is rather than being re-derived from the sided kernel: its downstream ecosystem (SeqSpec, ReadEquiv/compaction, EliasDelta, Peritext_Embed) consumes it directly, and §1 makes the duplication harmless, the all-R fragment equality transports any one-sided theorem to the sided instance when needed, at zero proof debt. **What remains of the Fugue half (deliberately out of scope here).** `sided_fold_subtree_convex` gives non-interleaving to any policy that keeps a run inside one subtree. Proving that the FUGUE RULE does so for backward runs ("right child of the left neighbor when it has no right children, else left child of the successor") needs a generation model the framework does not yet have: intent-level ops ("insert before position i"), the policy as a function of the replica-local tree, and a per-replica run notion. That layer, and the maximal-non-interleaving statement of Weidner & Kleppmann, is not covered by this file. -/ namespace Sal.ConditionedMRDTs open Sal.Emulation open Sal.EmbedRGA (OrderedPrefixCode keyLt key sym unaryCode Side SChain PosSChain PosChain sKey sBlock symR sidedCoordOf schain_subtree_convex sdisplay_iff_schainBefore keyLt_irrefl) set_option linter.unusedSectionVars false /-! ## §1 The all-R fragment at the instance -/ /-- Lift a one-sided op to the sided instance: inserts take side `R` and carry the symbol-mapped prefix; deletes are unchanged. -/ def liftOp : Op (EOp ℕ) → Op SOp | (t, r, .ins e π a) => (t, r, .ins e (π.map symR) a Side.R) | (t, r, .del x) => (t, r, .del x) /-- Lift a one-sided record: the coordinate goes through the R-band symbol map. -/ def liftRec (r : ERec ℕ) : SRec := (r.1, r.2.1, r.2.2.map symR) /-- The lifted sort key IS the one-sided sort key (`sym = symR` definitionally). -/ theorem liftRec_key (x : ERec ℕ) : sKey (liftRec x).2.2 = key x.2.2 := rfl theorem sIds_map_liftRec (s : EState ℕ) : sIds (s.map liftRec) = eIds s := by induction s with | nil => rfl | cons a as ih => simp only [List.map_cons, sIds, eIds, List.map_cons] at ih ⊢; rw [ih]; rfl /-- Sorted insertion commutes with the lift: the guards are the same `keyLt` calls. -/ theorem sInsert_liftRec (r : ERec ℕ) : ∀ (s : EState ℕ), sInsert (liftRec r) (s.map liftRec) = (eInsert r s).map liftRec | [] => rfl | x :: xs => by by_cases h : keyLt (key x.2.2) (key r.2.2) = true · rw [show eInsert r (x :: xs) = r :: x :: xs from by simp [eInsert, h], show sInsert (liftRec r) ((x :: xs).map liftRec) = liftRec r :: (x :: xs).map liftRec from by simp [sInsert, liftRec_key, h]] rfl · rw [show eInsert r (x :: xs) = x :: eInsert r xs from by simp [eInsert, h], show sInsert (liftRec r) ((x :: xs).map liftRec) = liftRec x :: sInsert (liftRec r) (xs.map liftRec) from by simp [sInsert, liftRec_key, h], List.map_cons, sInsert_liftRec r xs] /-- Deletion commutes with the lift. -/ theorem filter_map_liftRec (s : EState ℕ) (x : ℕ) : (s.map liftRec).filter (fun r => decide (r.1 ≠ x)) = (s.filter (fun r => decide (r.1 ≠ x))).map liftRec := by induction s with | nil => rfl | cons a as ih => simp only [ne_eq, decide_not] at ih by_cases h : a.1 = x <;> simp [liftRec, List.filter_cons, h, ih] /-- **The all-R fragment, at the instance**: the sided fold of a lifted history IS the one-sided fold, record for record. Unconditional, no honesty, no sortedness hypotheses: a pure simulation. -/ theorem sFold_liftOp (Γ : OrderedPrefixCode) : ∀ (ρ : List (Op (EOp ℕ))), sFold Γ (ρ.map liftOp) = (eFold Γ ρ).map liftRec := by intro ρ induction ρ using List.reverseRecOn with | nil => rfl | append_singleton ρ o ih => rw [show (ρ ++ [o]).map liftOp = ρ.map liftOp ++ [liftOp o] from by simp, sFold_snoc, eFold_snoc, ih] obtain ⟨ts, r, op⟩ := o cases op with | del x => show ((eFold Γ ρ).map liftRec).filter _ = _ exact filter_map_liftRec (eFold Γ ρ) x | ins e π a => show sUpdate Γ ((eFold Γ ρ).map liftRec) (ts, r, .ins e (π.map symR) a Side.R) = _ simp only [sUpdate, eUpdate] rw [sIds_map_liftRec] by_cases hmem : ts ∈ eIds (eFold Γ ρ) · rw [if_pos hmem, if_pos hmem] · rw [if_neg hmem, if_neg hmem, show ((ts, e, π.map symR ++ sBlock Γ (Side.R, ts - a)) : SRec) = liftRec (ts, e, π ++ Γ.enc (ts - a)) from by simp [liftRec, sBlock, List.map_append]] exact sInsert_liftRec _ _ /-- The reads agree: id-and-element sequence for id-and-element sequence. Chained with tier 3 and the compaction theorem, the all-R sided instance = the naive text buffer = the published tombstoned RGA. -/ theorem sided_allR_read_eq (Γ : OrderedPrefixCode) (ρ : List (Op (EOp ℕ))) : (sFold Γ (ρ.map liftOp)).map (fun r => (r.1, r.2.1)) = (eFold Γ ρ).map (fun r => (r.1, r.2.1)) := by rw [sFold_liftOp, List.map_map] rfl #print axioms sFold_liftOp /-! ## §2 Non-interleaving at the instance -/ /-- **Subtrees display as contiguous blocks, at the fold.** In any fold whose inserts are chain-generated (`chain_gen`'s shape: every insert's coordinate is a positive sided chain's coordinate with telescoping sum), a record `z` displaying strictly between two members `x, y` of the subtree rooted at `p` is itself in that subtree. `keyLt (sKey ·) (sKey ·)` is the display order of the canonical sorted state, so "between" is between in what the user reads. A replica's run whose ops chain, what the Fugue policy generates for forward and backward typing alike, is a subtree, so nothing concurrent ever interleaves into it. -/ theorem sided_fold_subtree_convex (Γ : OrderedPrefixCode) {ρ : List (Op SOp)} (chainOf : ℕ → SChain) (hch : ∀ o ∈ ρ, sIsIns o = true → PosSChain (chainOf o.1) ∧ sCoord Γ o = sidedCoordOf Γ (chainOf o.1) ∧ ((chainOf o.1).map Prod.snd).sum = o.1) {p : SChain} {x z y : SRec} (hx : x ∈ sFold Γ ρ) (hz : z ∈ sFold Γ ρ) (hy : y ∈ sFold Γ ρ) (hpx : ∃ ex, chainOf x.1 = p ++ ex) (hpy : ∃ ey, chainOf y.1 = p ++ ey) (hzx : keyLt (sKey z.2.2) (sKey x.2.2) = true) (hyz : keyLt (sKey y.2.2) (sKey z.2.2) = true) : ∃ ez, chainOf z.1 = p ++ ez := by obtain ⟨ox, hox, hix, hrx⟩ := s_fold_rec_sub Γ ρ x hx obtain ⟨oz, hoz, hiz, hrz⟩ := s_fold_rec_sub Γ ρ z hz obtain ⟨oy, hoy, hiy, hry⟩ := s_fold_rec_sub Γ ρ y hy obtain ⟨hpox, hcox, -⟩ := hch ox hox hix obtain ⟨hpoz, hcoz, -⟩ := hch oz hoz hiz obtain ⟨hpoy, hcoy, -⟩ := hch oy hoy hiy have ex1 : x.1 = ox.1 := by rw [hrx]; rfl have ez1 : z.1 = oz.1 := by rw [hrz]; rfl have ey1 : y.1 = oy.1 := by rw [hry]; rfl have ex2 : x.2.2 = sidedCoordOf Γ (chainOf x.1) := by rw [hrx]; exact hcox have ez2 : z.2.2 = sidedCoordOf Γ (chainOf z.1) := by rw [hrz]; exact hcoz have ey2 : y.2.2 = sidedCoordOf Γ (chainOf y.1) := by rw [hry]; exact hcoy have hpx1 : PosSChain (chainOf x.1) := ex1.symm ▸ hpox have hpz1 : PosSChain (chainOf z.1) := ez1.symm ▸ hpoz have hpy1 : PosSChain (chainOf y.1) := ey1.symm ▸ hpoy have hnexz : chainOf x.1 ≠ chainOf z.1 := by intro he rw [ex2, ez2, he, keyLt_irrefl] at hzx exact Bool.noConfusion hzx have hnezy : chainOf z.1 ≠ chainOf y.1 := by intro he rw [ez2, ey2, he, keyLt_irrefl] at hyz exact Bool.noConfusion hyz have hbxz : Sal.EmbedRGA.schainBefore (chainOf x.1) (chainOf z.1) := (sdisplay_iff_schainBefore Γ hpx1 hpz1 hnexz).mp (by rw [← ex2, ← ez2]; exact hzx) have hbzy : Sal.EmbedRGA.schainBefore (chainOf z.1) (chainOf y.1) := (sdisplay_iff_schainBefore Γ hpz1 hpy1 hnezy).mp (by rw [← ez2, ← ey2]; exact hyz) exact schain_subtree_convex p hpx hpy hbxz hbzy #print axioms sided_fold_subtree_convex /-! ## §3 SPOT: L19, contiguous, PASS and FAIL shaped The L19 trace under the Fugue policy's anchor choices, replayed through the datatype's own fold: two concurrent backward runs become L-chains and display as contiguous blocks, `[50, 30, 10, 61, 41, 21, 1]`. The FAIL pin is the one-sided fan's interleaved verdict on the same stamps. -/ namespace SidedSPOT /-- Anchor chains of the L19 trace (Fugue anchoring, unary code). -/ def ch1 : SChain := [(Side.R, 1)] def ch10 : SChain := ch1 ++ [(Side.L, 9)] def ch30 : SChain := ch10 ++ [(Side.L, 20)] def ch21 : SChain := ch1 ++ [(Side.L, 20)] def ch41 : SChain := ch21 ++ [(Side.L, 20)] def cOf (ch : SChain) : List ℕ := sidedCoordOf unaryCode ch /-- The L19 history: `1` at the root, then replica A types backward `10, 30, 50` (each an L-entry at the successor) while replica B types backward `21, 41, 61`. Elements = ids for readability; every insert carries its anchor's stored coordinate as the prefix. -/ def opsL19 : List (Op SOp) := [ (1, 0, .ins 1 [] 0 Side.R) , (10, 0, .ins 10 (cOf ch1) 1 Side.L) , (30, 0, .ins 30 (cOf ch10) 10 Side.L) , (50, 0, .ins 50 (cOf ch30) 30 Side.L) , (21, 1, .ins 21 (cOf ch1) 1 Side.L) , (41, 1, .ins 41 (cOf ch21) 21 Side.L) , (61, 1, .ins 61 (cOf ch41) 41 Side.L) ] /-- PASS: the display keeps each run contiguous. -/ theorem l19_blocks_contiguous : (sFold unaryCode opsL19).map (fun r => r.2.1) = [50, 30, 10, 61, 41, 21, 1] := by native_decide /-- Should-FAIL pin: the one-sided fan's interleaved verdict on the same stamps, `[61, 50, 41, 30, 21, 10, 1]`, is exactly what this datatype does NOT display. -/ theorem l19_not_interleaved : (sFold unaryCode opsL19).map (fun r => r.2.1) ≠ [61, 50, 41, 30, 21, 10, 1] := by native_decide end SidedSPOT end Sal.ConditionedMRDTs