Adds sw-block/ directory with:
- distsim: protocol correctness simulator (96 tests)
- cluster model with epoch fencing, barrier semantics, commit modes
- endpoint identity, control-plane flow, candidate eligibility
- timeout events, timer races, same-tick ordering
- session ownership tracking with ID-based stale fencing
- enginev2: standalone V2 sender/session implementation (63 tests)
- per-replica Sender with identity-preserving reconciliation
- RecoverySession with FSM phase transitions and session ID
- execution APIs: BeginConnect, RecordHandshake, BeginCatchUp,
RecordCatchUpProgress, CompleteSessionByID — all sender-authority-gated
- recovery outcome branching: zero-gap, catch-up, needs-rebuild
- assignment-intent orchestration with epoch fencing
- design docs: acceptance criteria, open questions, first-slice spec,
protocol development process
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
20 KiB
WAL V2 Scenarios
Date: 2026-03-26 Status: working scenario backlog Purpose: define the scenario set that proves why WAL V2 exists, what it must do better than WAL V1, and what it should handle better than rebuild-heavy systems
Execution note:
- active implementation planning for these scenarios lives under
../.private/phase/ design/is the design/source-of-truth view.private/phase/is the execution/checklist view forsw
Why This File Exists
V2 should not grow by adding random simulations.
Each new scenario should prove one of these claims:
- committed data is never lost
- uncommitted data is never falsely revived
- epoch and promotion lineage are safe
- short-gap recovery is cheaper and cleaner than rebuild
- catch-up vs rebuild boundary is explicit and correct
- historical data correctness is preserved
Scenario Sources
The backlog draws scenarios from three sources:
- V1 / V1.5 real failures
- real bugs and real-hardware gaps observed during Phase 12 / Phase 13
- these are the highest-value scenarios because they came from actual system behavior
- V2 design obligations
- scenarios required by the intended V2 protocol shape
- examples:
- reservations
- lineage-first promotion
- explicit catch-up vs rebuild boundary
- Distributed-systems adversarial cases
- scenarios not yet seen in production, but known to be dangerous
- examples:
- zombie primary
- partitions
- message reordering
- multi-promotion lineage chains
This file is the shared backlog for anyone extending:
sw-block/prototype/fsmv2/sw-block/prototype/volumefsm/sw-block/prototype/distsim/
For active development sequencing, see:
sw-block/.private/phase/phase-01.mdsw-block/.private/phase/phase-02.mdsw-block/design/v2-scenario-sources-from-v1.md
Current simulator note:
- current
distsimcoverage already includes:- changed-address restart comparison across
V1/V1.5/V2 - same-address transient outage comparison
- slow control-plane recovery comparison
- stale-endpoint rejection
- committed-prefix-aware promotion eligibility
- changed-address restart comparison across
V2 Goals
Compared with WAL V1, V2 should improve:
- state clarity
- recovery boundary clarity
- fencing and promotion correctness
- testability of distributed behavior
- proof of data correctness at a target
LSN
Compared with rebuild-heavy systems, V2 should improve:
- short-gap recovery cost
- explicit progress semantics
- catch-up vs rebuild decision quality
Scenario Format
Each scenario should eventually define:
- setup
- event sequence
- expected commit/ack behavior
- expected promotion/fencing behavior
- expected final data state at target
LSN
Where possible, use synthetic 4K writes with:
value = LSN
That makes correctness assertions trivial.
Priority 1: Commit Safety
These scenarios prove the most important distributed claim:
- if the system ACKed a write under the configured policy, that write is not lost
S1. ACK Then Primary Crash
Goal:
- prove a quorum-acknowledged write survives failover
Sequence:
- primary commits a write
- replicas durable-ACK enough nodes for policy
- primary crashes immediately
- coordinator promotes a valid replica
Expect:
- promoted node contains the committed
LSN - final state matches reference model at committed
LSN
S2. Non-Quorum Write Then Primary Crash
Goal:
- prove uncommitted data is not revived after failover
Sequence:
- primary accepts a write locally
- quorum durability is not reached
- primary crashes
- coordinator promotes another node
Expect:
- promoted node does not expose the uncommitted write
- committed
LSNstays at previous value
S3. Zombie Old Primary Is Fenced
Goal:
- prove old-epoch traffic cannot corrupt new lineage
Sequence:
- primary loses lease
- coordinator bumps epoch and promotes new primary
- old primary continues trying to send writes / barriers
Expect:
- all old-epoch traffic is rejected
- no stale write becomes committed under the new epoch
Priority 2: Short-Gap Recovery
These scenarios justify V2 over rebuild-heavy designs.
S4. Brief Disconnect, WAL Catch-Up Only
Goal:
- prove a short outage recovers via WAL catch-up, not rebuild
Sequence:
- replica disconnects briefly
- primary continues writing
- gap stays inside recoverable window
- replica reconnects and catches up
Expect:
CatchingUp -> PromotionHold -> InSync- no rebuild required
- final state matches reference at target
LSN
S5. Flapping Replica Stays Recoverable
Goal:
- prove transient disconnects do not force unnecessary rebuild
Sequence:
- replica disconnects and reconnects repeatedly
- gaps stay within reserved recoverable windows
Expect:
- replica may move between
Lagging,CatchingUp, andPromotionHold - replica does not enter
NeedsRebuildunless recoverability is actually lost
S6. Tail-Chasing Under Load
Goal:
- prove behavior when primary writes faster than catch-up rate
Sequence:
- replica reconnects behind
- primary continues writing quickly
- catch-up target may be reached or may fall behind again
Expect:
- explicit result:
- converge and promote
- or abort to
NeedsRebuild
- never silently pretend the replica is current
Priority 3: Catch-Up vs Rebuild Boundary
These scenarios justify the V2 recoverability model.
S7. Recovery Initially Possible, Then Reservation Expires
Goal:
- prove
check -> reserve -> recoveris enforced
Sequence:
- primary grants a recoverability reservation
- catch-up starts
- reservation expires or is revoked before completion
Expect:
- catch-up aborts
- replica transitions to
NeedsRebuild - no partial recovery is treated as success
S8. Current Extent Cannot Recover Old LSN
Goal:
- prove the historical correctness trap
Sequence:
- write block
B = 10atLSN 10 - later write block
B = 12atLSN 12 - attempt to recover state at
LSN 10from current extent
Expect:
- mismatch detected
- scenario must fail correctness check
S9. Snapshot + Tail Rebuild Works
Goal:
- prove correct long-gap reconstruction
Sequence:
- take snapshot at
cpLSN - later writes extend head
- lagging replica rebuilds from snapshot
- replay trailing WAL tail
Expect:
- final state matches reference at target
LSN
Priority 4: Quorum and Mixed Replica States
These scenarios justify V2 mode clarity.
S10. Mixed States Under sync_quorum
Goal:
- prove
sync_quorumremains available with mixed replica states
Sequence:
- one replica
InSync - one replica
CatchingUp - one replica
Rebuilding
Expect:
- writes may continue if durable quorum exists
- ACK gating follows quorum rules exactly
S11. Mixed States Under sync_all
Goal:
- prove
sync_allremains strict
Sequence:
- same mixed-state setup as above
Expect:
- writes/acks block or fail according to
sync_all - no silent downgrade to quorum or best effort
S12. Promotion Chooses Best Valid Lineage
Goal:
- prove promotion is correctness-first, not “highest apparent LSN wins”
Sequence:
- candidate nodes have different:
- flushed LSN
- rebuild state
- epoch lineage
- coordinator chooses a new primary
Expect:
- only a valid-lineage node is promotable
- stale or inconsistent node is rejected
Priority 5: Smart WAL / Recovery Classes
These scenarios justify V2’s future adaptive write path.
S13. WALInline Window Is Recoverable
Goal:
- prove inline WAL payload replay works directly
Sequence:
- missing range consists of
WALInlinerecords - planner grants reservation
Expect:
- catch-up allowed
- final state correct
S14. ExtentReferenced Payload Still Resolvable
Goal:
- prove direct-extent records can still support catch-up when pinned
Sequence:
- missing range includes
ExtentReferencedrecords - payload objects / generations are still resolvable
- reservation pins those dependencies
Expect:
- catch-up allowed
- final state correct
S15. ExtentReferenced Payload Lost
Goal:
- prove metadata alone is not enough
Sequence:
- missing range includes
ExtentReferencedrecords - metadata still exists
- payload object / version is no longer resolvable
Expect:
- planner returns
NeedsRebuild - catch-up is forbidden
Priority 6: Restart and Rebuild Robustness
These scenarios justify operational resilience.
S16. Replica Restarts During Catch-Up
Goal:
- prove restart does not corrupt catch-up state
Sequence:
- replica is catching up
- replica restarts
- reconnect and recover again
Expect:
- no false promotion
- resume or restart recovery cleanly
S17. Replica Restarts During Rebuild
Goal:
- prove rebuild interruption is safe
Sequence:
- replica is rebuilding from snapshot
- replica restarts mid-copy
Expect:
- rebuild aborts or restarts safely
- no partial base image is treated as valid
S18. Primary Restarts Without Failover
Goal:
- prove restart with same lineage is handled explicitly
Sequence:
- primary stops and restarts
- coordinator either preserves or changes epoch depending on policy
Expect:
- replicas react consistently
- no stale assumptions about previous sender sessions
S19. Chain Of Custody Across Multiple Promotions
Goal:
- prove committed data survives more than one failover lineage step
Sequence:
- primary
Acommits writes - fail over to
B Bcommits additional writes- fail over to
C
Expect:
Ccontains all writes committed byAandB- no committed data disappears across multiple promotions
- final state matches reference model at committed
LSN
S20. Network Partition With Concurrent Write Attempts
Goal:
- prove epoch fencing prevents split-brain writes during partition
Sequence:
- cluster partitions into two live sides
- old primary side continues trying to write
- coordinator promotes a new primary on the surviving side
- both sides attempt to send control/data traffic
Expect:
- only the current-epoch side can advance committed state
- stale-side writes are rejected or ignored
- no conflicting committed lineage appears
Suggested Implementation Order
Implement in this order:
S1ACK then primary crashS2non-quorum write then primary crashS3zombie old primary fencedS4brief disconnect with WAL catch-upS7reservation expiry aborts catch-upS10mixed-state quorum policyS9long-lag rebuild from snapshot + tailS13-S15Smart WAL recoverability
Coverage Matrix
Status values:
coveredpartialnot_startedneeds_richer_model
| Scenario | Package | Test / Artifact | Status | Notes |
|---|---|---|---|---|
S1 ACK then primary crash |
distsim |
TestQuorumCommitSurvivesPrimaryFailover |
covered |
quorum commit survives failover |
S2 non-quorum write then primary crash |
distsim |
TestUncommittedWriteNotPreservedAfterPrimaryLoss |
covered |
no false revival |
S3 zombie old primary fenced |
distsim |
TestZombieOldPrimaryWritesAreFenced |
covered |
stale epoch traffic ignored |
S4 brief disconnect, WAL catch-up only |
distsim |
TestReplicaCatchupFromPrimaryWAL |
covered |
short-gap recovery |
S5 flapping replica stays recoverable |
distsim |
TestS5_FlappingReplica_NoUnnecessaryRebuild, TestS5_FlappingWithStateTracking, TestS5_FlappingExceedsBudget_EscalatesToNeedsRebuild |
covered |
both recoverable flapping and explicit budget-exceeded escalation are now asserted |
S6 tail-chasing under load |
distsim |
TestS6_TailChasing_ConvergesOrAborts, TestS6_TailChasing_NonConvergent_Aborts, TestS6_TailChasing_NonConvergent_EscalatesToNeedsRebuild, TestP02_S6_NonConvergent_ExplicitStateTransition |
covered |
explicit non-convergent CatchingUp -> NeedsRebuild path now asserted |
S7 reservation expiry aborts catch-up |
fsmv2, volumefsm, distsim |
TestFSMReservationLostNeedsRebuild, TestModelReservationLostDuringCatchupAfterRebuild, TestReservationExpiryAbortsCatchup |
covered |
present at 3 layers |
S8 current extent cannot recover old LSN |
distsim |
TestCurrentExtentCannotRecoverOldLSN |
covered |
historical correctness trap |
S9 snapshot + tail rebuild works |
distsim |
TestReplicaRebuildFromSnapshotAndTail, TestSnapshotPlusTrailingReplayReachesTargetLSN |
covered |
long-gap reconstruction |
S10 mixed states under sync_quorum |
volumefsm, distsim |
TestModelSyncQuorumWithThreeReplicasMixedStates, TestSyncQuorumWithMixedReplicaStates |
covered |
quorum stays available |
S11 mixed states under sync_all |
distsim |
TestSyncAllBlocksWithMixedReplicaStates |
covered |
strict sync_all behavior |
S12 promotion chooses best valid lineage |
distsim |
TestPromotionUsesValidLineageNode, TestS12_PromotionChoosesBestLineage_NotHighestLSN, TestS12_PromotionRejectsRebuildingCandidate |
covered |
lineage-first promotion now exercised beyond simple LSN comparison |
S13 WALInline window recoverable |
distsim |
TestWALInlineRecordsAreRecoverable |
covered |
inline payload recoverability |
S14 ExtentReferenced payload resolvable |
distsim |
TestExtentReferencedResolvableRecordsAreRecoverable, TestMixedClassRecovery_FullSuccess |
covered |
recoverable direct-extent and mixed-class recovery case |
S15 ExtentReferenced payload lost |
distsim |
TestExtentReferencedUnresolvableForcesRebuild, TestRecoverableThenUnrecoverable, TestTimeVaryingAvailability |
covered |
metadata alone not enough; active recovery can transition from recoverable to unrecoverable |
S16 replica restarts during catch-up |
distsim |
TestReplicaRestartDuringCatchupRestartsSafely |
covered |
safe recovery restart |
S17 replica restarts during rebuild |
distsim |
TestReplicaRestartDuringRebuildRestartsSafely |
covered |
rebuild interruption safe |
S18 primary restarts without failover |
distsim |
TestS18_PrimaryRestart_SameLineage, TestS18_PrimaryRestart_ReplicasRejectOldEpoch, TestS18_PrimaryRestart_DelayedOldAck_DoesNotAdvancePrefix, TestS18_PrimaryRestart_InFlightBarrierDropped, TestP02_S18_DelayedAck_ExplicitRejection |
covered |
delayed stale ack rejection and committed-prefix stability are now asserted directly |
S19 chain of custody across promotions |
distsim |
TestS19_ChainOfCustody_MultiplePromotions, TestS19_ChainOfCustody_ThreePromotions |
covered |
multi-promotion lineage continuity covered |
S20 live partition with competing writes |
distsim |
TestS20_LivePartition_StaleWritesNotCommitted, TestS20_LivePartition_HealRecovers, TestS20_StalePartition_ProtocolRejectsStaleWrites, TestP02_S20_StaleTraffic_CommittedPrefixUnchanged |
covered |
stale-side protocol traffic is explicitly rejected and committed prefix remains unchanged |
Ownership Notes
When adding a scenario:
- add or extend the relevant prototype test:
fsmv2volumefsmdistsim
- update this file with:
- status
- package location
- keep correctness checks tied to:
- committed
LSN - reference model state
- committed
Current Coverage Snapshot
Already covered in some form:
- quorum commit survives primary failover
- uncommitted write not preserved after primary loss
- zombie old primary fenced by epoch
- lagging replica catch-up from primary WAL
- reservation expiry aborts catch-up in distributed sim
sync_quorumcontinues with one lagging replicasync_allblocks with one lagging replicasync_quorumwith mixed replica statessync_allwith mixed replica states- rebuild from snapshot + tail
- promotion uses valid lineage node
- flapping recoverable vs budget-exceeded rebuild path
- tail-chasing explicit escalation to rebuild
- restart during catch-up recovers safely
- restart during rebuild recovers safely
- primary restart delayed stale ack rejection
WALInlinerecoverabilityExtentReferencedresolvable vs unresolvable boundary- mixed-class Smart WAL recovery and time-varying payload availability
- delayed stale messages and selective drop behavior
- multi-node reservation expiry and rebuild-timeout behavior
- current extent cannot reconstruct old
LSN
Still important to add:
- explicit coordinator-driven candidate selection among competing valid/invalid lineages
- control-plane latency scenarios derived from
CP13-8 T4b - explicit V1 / V1.5 / V2 comparison scenarios for:
- changed-address restart
- same-address transient outage
- slow reassignment recovery
V1.5 Lessons To Add Or Strengthen
These come directly from WAL V1.5 / Phase 13 behavior and should be treated as high-priority scenario drivers.
L1. Replica Restart With New Receiver Port
Observed:
- replica VS restarts
- receiver comes back on a new random port
- primary background reconnect retries old address and fails
Implication:
- direct reconnect only works if replica address is stable
Backlog impact:
- strengthen
S18 - add a restart/address-change sub-scenario under
S20or a future network/control-plane recovery scenario
L2. Slow Control-Plane Reassignment Dominates Recovery
Observed:
- sync correctness preserved
- write availability recovery waits for heartbeat/reassignment cycle
Implication:
- "recoverable in theory" is not enough
- recovery latency is part of protocol quality
Backlog impact:
S5is now covered at current simulator level- strengthen
S18 - add long-running restart/rejoin timing scenarios
L3. Background Reconnect Helps Only Same-Address Recovery
Observed:
- background reconnect is useful for transient network failure
- not sufficient for process restart with address change
Implication:
- scenarios must distinguish:
- transient disconnect
- process restart
- address change
Backlog impact:
- keep
S4as transient disconnect - strengthen
S18with restart/address-stability cases
L4. Tail-Chasing And Retention Pressure Are Structural Risks
Observed:
- Phase 13 reasoning repeatedly exposed:
- lagging replica may pin WAL
- catch-up may not converge while primary keeps advancing
Implication:
- V2 must explicitly model convergence, abort, and rebuild boundaries
Backlog impact:
- strengthen
S6 - add multi-node retention / timeout variants
L5. Current Extent Is Not Historical State
Observed:
- using current extent to reconstruct old
LSNcan return later values
Implication:
- V2 must require version-correct base images or resolvable historical payloads
Backlog impact:
- already covered by
S8 - should remain a permanent regression scenario
Randomized Simulation
In addition to fixed scenarios, V2 should keep a randomized simulator suite.
Purpose:
- discover paths that were not explicitly written as named scenarios
- stress promotion, restart, and recovery ordering
- check invariants after each random step
Current prototype:
sw-block/prototype/distsim/random.gosw-block/prototype/distsim/random_test.go
Current invariants checked:
- current committed
LSNremains a committed prefix - promotable nodes match reference state at committed
LSN - current primary, if valid/running, matches reference state at committed
LSN
This does not replace named scenarios. It complements them.
Scenario Summary
When reviewing or adding scenarios, always record the source:
- from real V1/V1.5 behavior
- from explicit V2 design obligation
- from adversarial distributed-systems reasoning
The best scenarios are the ones that come from real failures first, then are generalized into V2 requirements.
Development Phases
Execution detail is tracked in:
sw-block/.private/phase/phase-01.mdsw-block/.private/phase/phase-02.md
High-level phase order:
- close explicit scenario backlog
S19S20
- strengthen missing lifecycle scenarios
S5S6S18- stronger
S12
- extend protocol-state simulation and version comparison
V1V1.5V2- stronger closure of current
partialscenarios
- strengthen random/adversarial simulation
- add timeout-based scenarios only when the execution path is modeled