Spaces:
Sleeping
Sleeping
| -- 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 | |