sprawling-documents 0.0.10

The rules of a document a person reads and edits through a sprawling city's page: versions, characters, windows and saves.
Documentation
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-- Copyright (c) 2026 2youg1 and the sprawling contributors

/-!
# edit:一次保存改了什么、留下什么、怎样收回

规定 `crates/documents/src/edit.rs`(`documents::Transaction`、`Edit`、`Applied`)。Rust 代码是「怎样守住」的权威;本模型是「必须守住哪些性质」的权威。

Rust 的 `Transaction` 是一串按文档序排好、互不重叠的半开字节区间加各自的替换字节。本模型把同一件事写成从头到尾的一串 `Piece`:两段编辑之间的空隙是 `keep n`,一段编辑是 `replace old new`(换掉源文接下来的 `old` 个字节)。两种写法一一对应:按序走 Rust 的编辑,空隙长 `start - cursor` 记一个 `keep`,编辑本身记一个 `replace (end - start) bytes`;末尾没被点名的字节在两边都原样留下。字节在模型里是 `Nat`,因为性质与字节的取值范围无关。

证明的性质:

1. **编辑之外的字节原样留下**(`apply_keep`、`apply_nil`):`keep n` 抄的就是源文的前 `n` 个字节,没被点名的尾巴原样留下(A1 的模型一半)。
2. **撤销还原基线**(`undo_restores`):一次保存交回的逆事务作用在新版本上,得到的就是原来那一版,逐字节(D9)。
3. **同一基线的第二次保存被拒**(`later_is_refused`):带基线的接受只在源文的摘要等于基线时落;第一次保存改动了字节之后,第二个从同一基线出发的写者被拒(A3 的模型一半,D8)。摘要是参数 `digest`,它的单射性是假设 `inj`:这是 BLAKE3 的抗碰撞性,模型不证明它。
4. **基线对得上就落**(`first_is_accepted`):守卫不是恒拒。

**拿掉基线守卫,第二个写者就盖掉第一个**(`withoutBaseline_overwrites`):本模型咬得动的演示。
-/

namespace Documents.Edit

/-- 一个字节;取值范围与性质无关。 -/
abbrev Bytes := List Nat

/-- 从源文头上走过的一步:留下 `n` 个字节,或把接下来的 `old` 个字节换成 `new`。 -/
inductive Piece where
  | keep (n : Nat)
  | replace (old : Nat) (new : Bytes)

/-- 照 pieces 从左到右走源文,末尾没被点名的字节原样留下。 -/
def apply : List Piece → Bytes → Bytes
  | [], s => s
  | .keep n :: t, s => s.take n ++ apply t (s.drop n)
  | .replace old new :: t, s => new ++ apply t (s.drop old)

/-- 逆事务:在新版本的坐标里,把每段 `new` 换回源文原来那一段。 -/
def inverse : List Piece → Bytes → List Piece
  | [], _ => []
  | .keep n :: t, s => .keep n :: inverse t (s.drop n)
  | .replace old new :: t, s => .replace new.length (s.take old) :: inverse t (s.drop old)

/-- 每一步都落在源文之内:Rust 的 `E_INVALID_ARGS`(编辑越过末尾)排除的就是不满足它的事务。 -/
def fits : List Piece → Bytes → Prop
  | [], _ => True
  | .keep n :: t, s => n ≤ s.length ∧ fits t (s.drop n)
  | .replace old _ :: t, s => old ≤ s.length ∧ fits t (s.drop old)

theorem apply_nil (s : Bytes) : apply [] s = s := rfl

theorem apply_keep (n : Nat) (t : List Piece) (s : Bytes) :
    apply (.keep n :: t) s = s.take n ++ apply t (s.drop n) := rfl

theorem undo_restores (t : List Piece) (s : Bytes) (h : fits t s) :
    apply (inverse t s) (apply t s) = s := by
  induction t generalizing s with
  | nil => rfl
  | cons p t ih =>
    cases p with
    | keep n =>
      obtain ⟨hn, ht⟩ := h
      have hlen : (s.take n).length = n := by
        simp [List.length_take, Nat.min_eq_left hn]
      simp only [apply, inverse]
      rw [List.take_left' hlen, List.drop_left' hlen, ih (s.drop n) ht, List.take_append_drop]
    | replace old new =>
      obtain ⟨_, ht⟩ := h
      simp only [apply, inverse]
      rw [List.drop_left, ih (s.drop old) ht, List.take_append_drop]

/-- 带基线的接受:源文的摘要等于基线才落,否则什么也不落。 -/
def accept {α : Type} [DecidableEq α] (digest : Bytes → α) (baseline : α)
    (t : List Piece) (s : Bytes) : Option Bytes :=
  if digest s = baseline then some (apply t s) else none

theorem first_is_accepted {α : Type} [DecidableEq α] (digest : Bytes → α)
    (t : List Piece) (s : Bytes) : accept digest (digest s) t s = some (apply t s) := by
  simp [accept]

theorem later_is_refused {α : Type} [DecidableEq α] (digest : Bytes → α)
    (inj : ∀ a b, digest a = digest b → a = b)
    (s : Bytes) (t₁ t₂ : List Piece) (moved : apply t₁ s ≠ s) :
    accept digest (digest s) t₂ (apply t₁ s) = none := by
  unfold accept
  split
  · rename_i same
    exact absurd (inj _ _ same) moved
  · rfl

/-!
## 咬得动的演示

不看基线(每次都落),第二个从同一版本出发的写者把第一个写者的改动盖掉:第一个写者把 `[1]` 换成 `[2]`,第二个把同一个字节换成 `[3]`,最后留下的是 `[3]`,`[2]` 不见了。
-/

def acceptWithoutBaseline (t : List Piece) (s : Bytes) : Option Bytes :=
  some (apply t s)

theorem withoutBaseline_overwrites :
    acceptWithoutBaseline [.replace 1 [3]] (apply [.replace 1 [2]] [1]) = some [3] := by
  decide

end Documents.Edit