lean-migrate / lean /SagaSpec.lean
Hrushi's picture
Upload folder using huggingface_hub
16f1328 verified
-- File: lean/SagaSpec.lean
-- Payment Saga State Machine Specification
-- Agents migrate Express.js source to FastAPI/Python.
-- Task 3: agents must also submit a LEAN proof of no_double_charge.
namespace SagaSpec
-- Payment saga states
inductive SagaState
| Idle
| Reserved -- funds reserved
| Authorized -- payment authorized
| Captured -- payment captured
| Settled -- fully settled
| Compensating -- rollback in progress
| Compensated -- rollback complete
| Failed
deriving BEq, Repr, DecidableEq
-- Events that drive state transitions
inductive SagaEvent
| Reserve
| Authorize
| Capture
| Settle
| CompensateReserve
| CompensateAuthorize
| CompensateCapture
| Fail
deriving BEq, Repr, DecidableEq
-- The state machine transition function
-- This is the ground truth for agents to replicate
def transition (s : SagaState) (e : SagaEvent) : SagaState :=
match s, e with
| .Idle, .Reserve => .Reserved
| .Reserved, .Authorize => .Authorized
| .Authorized, .Capture => .Captured
| .Captured, .Settle => .Settled
| .Reserved, .CompensateReserve => .Compensated
| .Authorized, .CompensateAuthorize => .Compensating
| .Compensating, .CompensateReserve => .Compensated
| .Captured, .CompensateCapture => .Compensating
| _, .Fail => .Failed
| s, _ => s -- invalid events are no-ops
-- A valid payment reaches Settled via the happy path
def happyPath : List SagaEvent :=
[.Reserve, .Authorize, .Capture, .Settle]
def runSaga (events : List SagaEvent) : SagaState :=
events.foldl transition .Idle
-- ── Critical safety property ────────────────────────────────────────
-- A payment can only be charged (Captured) once.
-- "Charged" means reaching the Captured or Settled state.
-- Use pattern matching (not ==) to generate clean simp lemmas.
def isCharged (s : SagaState) : Bool :=
match s with
| .Captured | .Settled => true
| _ => false
-- Once charged, re-sending Capture is a no-op (idempotency)
theorem capture_idempotent (s : SagaState) (h : isCharged s = true) :
transition s .Capture = s := by
cases s <;> simp_all [transition, isCharged]
-- The happy path reaches Settled
theorem happy_path_settles :
runSaga happyPath = .Settled := by
simp [runSaga, happyPath, transition]
-- From Settled, any non-Fail event leaves you in a charged state
-- (Fail transitions to Failed which is a terminal error, not a double-charge)
theorem no_double_charge_from_settled (e : SagaEvent) (he : e β‰  .Fail) :
isCharged (transition .Settled e) = true := by
cases e <;> simp_all [transition, isCharged]
-- ── The theorem agents must prove in Task 3 (30% of score) ──────────
-- After running the happy path, the state is charged exactly once.
-- Agents submit a LEAN proof of this theorem alongside their Python code.
theorem no_double_charge :
runSaga happyPath = .Settled ∧ isCharged (runSaga happyPath) = true := by
constructor
Β· exact happy_path_settles
Β· rw [happy_path_settles]; simp [isCharged]
end SagaSpec