Files
Baradb/formal-verification/replication.tla
T
dimgigov 422df08ab9 feat: clean build + crossmodal.tla + TLA+ symmetry (v1.0.0-prep)
Build warnings cleanup (0 warnings):
- Suppress threadpool deprecation warning (baradadb.nim)
- Remove unused 'os' import (logging.nim)
- Fix ImplicitDefaultValue for newNode params (ast.nim)
- Add explicit cstring cast for posix.open (wal.nim)
- Fix HoleEnumConv for MsgKind parsing (server.nim)

TLA+ Formal Verification:
- Add symmetry reduction (Permutations) to all 9 existing specs
- Add SYMMETRY directive to all .cfg files
- New crossmodal.tla: cross-modal consistency spec
  * MetadataVectorConsistency, HybridResultValid
  * CommittedAtomicity, AbortedAtomicity, TxnStateValid
- New models/crossmodal.cfg

Tests:
- Add Cross-Modal TLA+ Faithfulness tests (4 tests)

Docs:
- Update PLAN.md and BUG_AUDIT.md with completed tasks
2026-05-13 09:24:04 +03:00

164 lines
5.6 KiB
Plaintext

-------------------------------- MODULE replication --------------------------------
(*
TLA+ specification of the BaraDB replication manager
(core/replication.nim) supporting Async, Sync, and Semi-sync modes.
Key properties verified:
- MonotonicLsn : applied LSN never moves backwards (temporal).
- AcksRemovePending: a replica that acked an LSN is not pending for it.
- PendingAreKnown : all pending acks refer to valid replicas.
- SyncDurability : in sync mode, appliedLsn only advances when 0 pending.
- SemiSyncQuorum : in semi-sync mode, ack count >= min(MaxSyncCount, Connected).
- AppliedLteCurrent : appliedLsn never exceeds currentLsn.
- AckedIsConnected : only connected replicas can ack.
*)
EXTENDS Integers, Sequences, FiniteSets, TLC
CONSTANTS Replicas, \* set of replica IDs
MaxLsn, \* bound LSN values for model checking
MaxSyncCount \* bound semi-sync count
ASSUME IsFiniteSet(Replicas)
VARIABLES
mode, \* mode ∈ {"Async", "Sync", "SemiSync"}
replicaState, \* replicaState[r] ∈ {"Disconnected", "Connected"}
currentLsn, \* currentLsn ∈ Nat
pendingAcks, \* pendingAcks[l] ⊆ Replicas (LSNs waiting for acks)
appliedLsn, \* appliedLsn ∈ Nat
ackedBy \* ackedBy[l] ⊆ Replicas (who acked which LSN)
vars == <<mode, replicaState, currentLsn, pendingAcks, appliedLsn, ackedBy>>
-----------------------------------------------------------------------------
\* Helper operators
Max(a, b) == IF a > b THEN a ELSE b
Min(a, b) == IF a < b THEN a ELSE b
ConnectedReplicas == {r \in Replicas : replicaState[r] = "Connected"}
-----------------------------------------------------------------------------
\* Initial state
Init ==
/\ mode \in {"Async", "Sync", "SemiSync"}
/\ replicaState = [r \in Replicas |-> "Disconnected"]
/\ currentLsn = 0
/\ pendingAcks = [l \in 0..MaxLsn |-> {}]
/\ appliedLsn = 0
/\ ackedBy = [l \in 0..MaxLsn |-> {}]
-----------------------------------------------------------------------------
\* State transitions
\* A replica comes online.
Connect(r) ==
/\ replicaState[r] = "Disconnected"
/\ replicaState' = [replicaState EXCEPT ![r] = "Connected"]
/\ UNCHANGED <<mode, currentLsn, pendingAcks, appliedLsn, ackedBy>>
\* A replica goes offline.
Disconnect(r) ==
/\ replicaState[r] = "Connected"
/\ replicaState' = [replicaState EXCEPT ![r] = "Disconnected"]
/\ UNCHANGED <<mode, currentLsn, pendingAcks, appliedLsn, ackedBy>>
\* A new LSN is produced by the primary.
WriteLsn ==
/\ currentLsn < MaxLsn
/\ currentLsn' = currentLsn + 1
/\ LET newLsn == currentLsn + 1
conn == ConnectedReplicas
IN IF mode = "Sync" /\ conn /= {}
THEN pendingAcks' = [pendingAcks EXCEPT ![newLsn] = conn]
ELSE IF mode = "SemiSync" /\ conn /= {}
THEN pendingAcks' = [pendingAcks EXCEPT ![newLsn] =
CHOOSE s \in SUBSET conn :
Cardinality(s) = Min(MaxSyncCount, Cardinality(conn))]
ELSE pendingAcks' = pendingAcks
/\ UNCHANGED <<mode, replicaState, appliedLsn, ackedBy>>
\* Replica r acknowledges LSN l.
AckLsn(r, l) ==
/\ replicaState[r] = "Connected"
/\ l \in 1..currentLsn
/\ r \in pendingAcks[l]
/\ ackedBy' = [ackedBy EXCEPT ![l] = @ \cup {r}]
/\ pendingAcks' = [pendingAcks EXCEPT ![l] = @ \ {r}]
/\ IF pendingAcks'[l] = {}
THEN appliedLsn' = Max(appliedLsn, l)
ELSE appliedLsn' = appliedLsn
/\ UNCHANGED <<mode, replicaState, currentLsn>>
\* Switch replication mode.
SwitchMode(newMode) ==
/\ newMode \in {"Async", "Sync", "SemiSync"}
/\ mode' = newMode
/\ UNCHANGED <<replicaState, currentLsn, pendingAcks, appliedLsn, ackedBy>>
-----------------------------------------------------------------------------
\* Next-state relation
Next ==
\/ \E r \in Replicas : Connect(r)
\/ \E r \in Replicas : Disconnect(r)
\/ WriteLsn
\/ \E r \in Replicas : \E l \in 1..MaxLsn : AckLsn(r, l)
\/ \E newMode \in {"Async", "Sync", "SemiSync"} : SwitchMode(newMode)
-----------------------------------------------------------------------------
\* Safety properties
\* The applied LSN is monotonically non-decreasing (temporal).
MonotonicLsn ==
[][appliedLsn' >= appliedLsn]_vars
\* A replica that has acked an LSN is no longer pending for it.
AcksRemovePending ==
\A l \in 1..currentLsn :
\A r \in Replicas :
r \in ackedBy[l] => r \notin pendingAcks[l]
\* All pending acks reference valid replica IDs.
PendingAreKnown ==
\A l \in 1..currentLsn :
pendingAcks[l] \subseteq Replicas
\* In sync mode, any applied LSN has zero pending acks.
SyncDurability ==
\/ mode /= "Sync"
\/ appliedLsn = 0
\/ \A l \in 1..appliedLsn : pendingAcks[l] = {}
\* In semi-sync mode, each pending LSN has at most MaxSyncCount replicas.
SemiSyncQuorum ==
IF currentLsn > 0
THEN \A l \in 1..currentLsn : Cardinality(pendingAcks[l]) <= MaxSyncCount + 1
ELSE TRUE
\* Applied LSN never exceeds current LSN.
AppliedLteCurrent ==
appliedLsn <= currentLsn
\* Only connected replicas appear in ackedBy.
AckedIsConnected ==
IF currentLsn > 0
THEN \A l \in 1..currentLsn : \A r \in ackedBy[l] : replicaState[r] = "Connected"
ELSE TRUE
\* Type invariant
TypeOk ==
/\ mode \in {"Async", "Sync", "SemiSync"}
/\ replicaState \in [Replicas -> {"Disconnected", "Connected"}]
/\ currentLsn \in 0..MaxLsn
/\ pendingAcks \in [0..MaxLsn -> SUBSET Replicas]
/\ appliedLsn \in 0..MaxLsn
/\ ackedBy \in [0..MaxLsn -> SUBSET Replicas]
\* Symmetry reduction for model checking.
Symmetry == Permutations({r1, r2, r3})
=============================================================================