Skip to content

EPaxos: footprint-aware certified checkpoints#14

Merged
lemon-mint merged 9 commits into
mainfrom
feat/epaxos-footprint-checkpoint
Jul 15, 2026
Merged

EPaxos: footprint-aware certified checkpoints#14
lemon-mint merged 9 commits into
mainfrom
feat/epaxos-footprint-checkpoint

Conversation

@lemon-mint

@lemon-mint lemon-mint commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

  • replace conflict-key command metadata with canonical point/span/all footprints and deterministic cycle keys
  • cut wire/storage semantics to MEP3 with voter-incarnation authentication and Pebble v9 migration
  • add ordered Ready.Apply, durable command results, certified checkpoints, atomic protocol compaction, and snapshot-plus-delta restart
  • preserve each receiver's local compaction floor when accepting peer certificates or checkpoint offers
  • retry prepared checkpoint votes and reactively return the latest certificate/offer after healed partitions
  • require nonempty application snapshot handles so every certified checkpoint remains transferable
  • add KV checkpoint transfer/smoke coverage and bounded EPaxosCertifiedCompaction TLC profiles

Rollout

  • breaking cutover: no MEP2/MEP3 mixed-version rollout
  • Pebble data migrates through the versioned v9 path
  • checkpoint descriptor/certificate remains peer-authenticated; application snapshot handle and CompactedThrough remain receiver-local

Verification

  • go vet ./...
  • golangci-lint run ./...
  • (cd examples/kv && golangci-lint run ./...)
  • tests/ci.sh
  • python3 -m unittest tests.tla_model_check_runner_test
  • coverage: epaxos 87.6%, examples/kv 90.5%
  • TLC fast profile: 32 jobs
  • deterministic isolate-through-checkpoint/heal convergence regression

Release boundary

The authoritative decision remains No-go. The single open release row is broader unbounded Go/TLA refinement and checked action correspondence, including the recorded TLAPS and trace-replay gaps plus external evidence requirements.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added certified checkpointing, snapshot installation, and durable compaction.
    • Added content-addressed application snapshots for the KV example.
    • Added ordered reads and scans with deterministic logical range handling.
    • Added command footprints supporting points, spans, and global scopes.
    • Added explicit caller-supplied timing for time-ordered processing.
  • Enhancements

    • Ready processing now separates application effects, snapshots, checkpoints, and compaction.
    • Strengthened restart, durability, validation, and command deduplication behavior.
    • Updated wire compatibility and malformed-input handling.

Walkthrough

Changes

EPaxos now uses explicit entry kinds and canonical point/span footprints, caller-supplied TOQ time, Ready.Apply, checkpoint snapshots, certified compaction, and frontier-aware storage. The KV example adds ordered reads, durable command-result deduplication, application snapshot transfer, and checkpoint-aware transport. Tests, simulations, documentation, and bounded TLA+ evidence were updated accordingly.

Suggested reviewers: metaphorics

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.27% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the PR’s footprint-aware certified checkpoint work.
Description check ✅ Passed The description matches the changeset and summarizes the checkpoint, storage, and MEP3 cutover work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/epaxos-footprint-checkpoint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread examples/kv/epaxos_storage.go Fixed
@coderabbitai
coderabbitai Bot requested a review from metaphorics July 15, 2026 17:23
@lemon-mint
lemon-mint merged commit 4dd453f into main Jul 15, 2026
6 checks passed
@lemon-mint
lemon-mint deleted the feat/epaxos-footprint-checkpoint branch July 15, 2026 18:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
tests/faultcampaign/invariants.go (1)

216-225: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

commandsConflict only understands one of three footprint kinds.

Footprint has Points, Spans, and All (epaxos/footprint.go). This function only walks Points. A command with Footprint.All = true conflicts with everything by definition, and range Spans conflict on overlap — neither is checked. Meanwhile sameChosenTuple, three functions up in this exact same file, already knows how to compare all three fields correctly. This is an internal inconsistency, not a stylistic nit.

Right now it's dormant because CommandForPut/CommandForTxn in the kv example only ever populate Points, so the campaign never actually exercises All/Spans. That's exactly the problem: the invariant checker silently passes even where it has zero power to catch a real conflict-ordering bug for two of the three footprint representations this whole PR was built to support. Don't wait for someone to add a range-scan command and discover the checker was toothless the whole time.

🐛 Proposed fix (at minimum handle `All`; `Spans` overlap needs real interval logic on top of this)
 func commandsConflict(left, right epaxos.Command) bool {
+	if left.Footprint.All || right.Footprint.All {
+		return true
+	}
 	for _, leftKey := range left.Footprint.Points {
 		for _, rightKey := range right.Footprint.Points {
 			if bytes.Equal(leftKey, rightKey) {
 				return true
 			}
 		}
 	}
+	// TODO: Spans-vs-Spans range overlap and Points-vs-Spans containment are
+	// still unchecked here.
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/faultcampaign/invariants.go` around lines 216 - 225, Update
commandsConflict to evaluate all Footprint representations, not only Points:
return true when either command has Footprint.All, retain point equality checks,
and add span-overlap detection using the same interval semantics as the existing
footprint comparison logic. Reuse sameChosenTuple or its established
span-comparison behavior where appropriate so conflicts match the three-field
Footprint contract.
examples/kv/cmd/kvnode/main.go (1)

1664-1715: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

GET's ordered-read path lies about the failure mode - 500 instead of 503.

Look at this branch versus everything else in the file. PUT (line 1726), DELETE (line 1736), Txn (line 1809), and even Scan's own ordered branch two hundred lines below (1909) all special-case proposeAndWait's error and return 503 Service Unavailable - that's the signal a client needs to know "retry me, don't treat this as broken." GET just lets the propose error fall through the same generic if err != nil bucket as a decode failure and slaps a 500 on it. That's not a stylistic quirk, that's a correctness regression in your own error contract, and you wrote the correct version four times elsewhere in this same file.

🐛 Fix: make GET match its four siblings
 		} else {
 			command := kv.CommandForGet(uint64(s.id), s.next(), key)
-			if err = s.proposeAndWait(ctx, command); err == nil {
-				var result kv.OrderedGetResult
-				var found bool
-				value, found, err = s.db.CommandResult(command.ID)
-				if err == nil && !found {
-					err = fmt.Errorf("ordered read completed without a durable result")
-				}
-				if err == nil {
-					result, err = kv.DecodeOrderedGetResult(value)
-					value, ok = result.Value, result.Found
-				}
-			}
+			if proposeErr := s.proposeAndWait(ctx, command); proposeErr != nil {
+				http.Error(w, proposeErr.Error(), http.StatusServiceUnavailable)
+				return
+			}
+			var result kv.OrderedGetResult
+			var found bool
+			value, found, err = s.db.CommandResult(command.ID)
+			if err == nil && !found {
+				err = fmt.Errorf("ordered read completed without a durable result")
+			}
+			if err == nil {
+				result, err = kv.DecodeOrderedGetResult(value)
+				value, ok = result.Value, result.Found
+			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/cmd/kvnode/main.go` around lines 1664 - 1715, Update the
ordered-read branch in handleKV’s GET case so errors returned by proposeAndWait
are sent as 503 Service Unavailable, matching the PUT, DELETE, transaction, and
scan paths. Keep decode, CommandResult, and other non-proposal failures on the
existing 500 response path.
examples/kv/cluster.go (1)

387-454: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Compare Kind, ConfChange, and ProtocolControl here too. sameEPaxosRecordTuple and sameChosenEPaxosRecord still ignore the fields that were moved off Command, so a checkpoint record can differ in entry type or control bytes and still pass these equality checks. This is checkpoint-validation code; it needs the same record-value comparison the core uses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/cluster.go` around lines 387 - 454, The equality helpers
sameEPaxosRecordTuple and sameChosenEPaxosRecord must also compare each record’s
Kind, ConfChange, and ProtocolControl fields. Add these comparisons using the
same value semantics as the core record comparison, while preserving all
existing comparisons and helper behavior.
epaxos/sim_test.go (1)

163-176: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The test transports are masking broken MEP3 wire output.

Protocol-generated messages must reach RawNode.Step unchanged; canonicalTestMessage can repair invalid checksums or voter-incarnation fields and let broken production output pass.

  • epaxos/sim_test.go#L163-L176: pass the emitted Message directly to Step.
  • epaxos/sim_test.go#L331-L340: step the Ready.Messages duplicate directly; build a separate fixture for target corruption.
  • epaxos/stress_test.go#L109-L116: pass the emitted stress message directly to Step.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/sim_test.go` around lines 163 - 176, The test transports currently
normalize protocol messages and hide invalid production wire output. In
epaxos/sim_test.go lines 163-176, update simCluster.deliver to pass the emitted
Message directly to RawNode.Step; in epaxos/sim_test.go lines 331-340, step the
Ready.Messages duplicate directly and create a separate fixture for target
corruption; in epaxos/stress_test.go lines 109-116, pass the emitted stress
message directly to Step. Remove canonicalTestMessage from these transport paths
while preserving explicit corruption fixtures.
🟡 Other comments (2)
examples/kv/cmd/kvnode/main_test.go-120-123 (1)

120-123: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set the record kind or this test exercises the wrong failure.

The fixture leaves InstanceRecord.Kind at zero. With the new schema, startup can reject the unknown entry kind before reaching the intended ambiguous legacy timing-domain validation.

Proposed fix
 		Status:       epaxos.StatusAccepted,
+		Kind:         epaxos.EntryCommand,
 		Seq:          1,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/cmd/kvnode/main_test.go` around lines 120 - 123, Update the
InstanceRecord fixture in the relevant test setup to explicitly assign the
legacy record kind, ensuring startup reaches the intended ambiguous legacy
timing-domain validation instead of failing on an unknown zero-value kind.
EPAXOS.MD-128-128 (1)

128-128: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stop calling opaque payloads canonical.

Only the footprint has a canonical form. This wording contradicts Line 34 and may make embedders transform payload bytes before transferring ownership.

Proposed clarification
-With `ZeroCopyProposals`, the caller transfers already-canonical payload, footprint, and cycle buffers
+With `ZeroCopyProposals`, the caller transfers payload, canonical-footprint, and cycle-key buffers
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@EPAXOS.MD` at line 128, Update the ZeroCopyProposals documentation to state
that only footprint buffers are already canonical; describe payload buffers as
transferred or aliased input bytes without calling them canonical. Ensure the
wording remains consistent with the canonical-form guidance near Line 34 and
does not imply embedders must transform payload data before ownership transfer.
🧹 Nitpick comments (8)
epaxos/message.go (1)

536-537: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Local var commandEmpty shadows the package function commandEmpty() it calls on the same line.

commandEmpty := m.Kind == 0 && commandEmpty(m.Command) && ... compiles and works — Go resolves the RHS call against the outer-scope function since the new variable's scope only starts at the end of the declaration — but it's a landmine for the next person who greps for commandEmpty or edits nearby code expecting the identifier to mean one specific thing.

✏️ Suggested rename
-	commandEmpty := m.Kind == 0 && commandEmpty(m.Command) &&
+	valueIsEmpty := m.Kind == 0 && commandEmpty(m.Command) &&
 		m.ConfChange == (ConfChange{}) && len(m.ProtocolControl) == 0

(and rename the ~6 later uses of the local commandEmpty bool in this function accordingly)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/message.go` around lines 536 - 537, Rename the local boolean declared
in the message emptiness check to avoid shadowing the package-level commandEmpty
function, and update all later references to that boolean within the same
function. Keep the commandEmpty(m.Command) function call unchanged.
epaxos/footprint.go (1)

212-243: 🚀 Performance & Scalability | 🔵 Trivial

footprintsConflict ignores that both inputs are already sorted.

Nested-loop compare over Points×Points, Points×Spans, Spans×Spans — up to 128×128 bytes.Compare/bytes.Equal calls per side given the wire limits. Both a and b are canonicalized (sorted, deduped) by the time they reach this function, so a two-pointer merge sweep would get this down to O(n+m) instead of O(n·m). Not flagging as urgent since footprints are typically tiny in practice and this doesn't appear to sit on the conflict_engine.go hot path (that uses the interval index instead) — just worth tightening if this ever gets called per-instance under real load.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/footprint.go` around lines 212 - 243, Update footprintsConflict to
exploit the sorted, deduplicated Points and Spans by replacing the nested
comparisons with linear two-pointer merge sweeps. Preserve the existing All
handling and half-open interval semantics, covering point-point, point-span, and
span-span conflicts while reducing each comparison phase to O(n+m).
epaxos/conflict_engine_test.go (1)

191-193: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test oracle doesn't know about Footprint.All — the fuzzer never exercises it.

Four helpers in this file (assertConflictEngineMatchesModel's wantGlobal/wantKeyLanes, modelKeyMax, assertExactKeyPostings) gate on entryHasGlobalConflictScope(rec.Kind) alone. That's the narrow, Kind-only predicate. The engine itself gates on recordHasGlobalConflictScope(rec), which is entryHasGlobalConflictScope(rec.Kind) || (rec.Kind == EntryCommand && rec.Command.Footprint.All) — compare engineModelAttrs at line 268, which correctly uses entryHasGlobalConflictScope(query.Kind) || query.Command.Footprint.All.

Since randomFoldRecord/randomConflictRecord never generate Footprint.All: true either, this 2000-iteration property fuzzer (TestConflictEngineRandomFoldDomination) never actually tests the group-wide-scope command path at all — one of the three footprint forms this PR introduces. If someone ever adds an All:true case to the generators without fixing these four call sites first, the oracle would silently produce wrong expectations and either mask a real engine bug or fail on a correct engine.

Fix: use recordHasGlobalConflictScope(rec) consistently in these four spots, and add a Footprint.All case to the record generators so this path actually gets fuzzed.

Also applies to: 217-237, 338-349, 630-646

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/conflict_engine_test.go` around lines 191 - 193, Update the four
oracle helpers—assertConflictEngineMatchesModel’s wantGlobal/wantKeyLanes logic,
modelKeyMax, and assertExactKeyPostings—to use recordHasGlobalConflictScope(rec)
instead of the Kind-only entryHasGlobalConflictScope(rec.Kind) predicate. Extend
randomFoldRecord and randomConflictRecord with generated EntryCommand records
whose Command.Footprint.All is true, ensuring the group-wide scope path is
exercised by the property fuzzer.
examples/kv/epaxos_storage.go (4)

512-521: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

validateApplicationSnapshot runs twice per SnapshotInstall Ready.

DB.ApplyReady calls db.validateApplicationSnapshot(rd.Snapshot.Checkpoint) here, then installApplicationSnapshot (line 217-218) calls the exact same function again a few lines later in the same ApplyReady. Each call re-fetches the bundle from Pebble and re-hashes the entire application snapshot with blake3, then re-parses every entry. For anything but a trivially small snapshot that's a wasted full pass. Either skip the pre-check for SnapshotInstall (since install re-validates anyway) or thread the already-validated entries through instead of re-deriving them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/epaxos_storage.go` around lines 512 - 521, Update DB.ApplyReady’s
snapshot validation flow to avoid calling validateApplicationSnapshot twice for
SnapshotInstall: skip the pre-check for epaxos.SnapshotInstall because
installApplicationSnapshot re-validates the checkpoint, while retaining
validation for other supported snapshot modes.

1084-1096: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New v9 early-return makes the old v9 terminal check dead code.

This new if version == epaxosRecordCodec branch (JSON path) always returns before falling through to the legacy binary-decode path below. But further down, the pre-existing block:

if version == epaxosRecordCodec {
    if epaxos.VerifyRecordChecksum(rec) {
        return rec, nil
    }
    return epaxos.InstanceRecord{}, epaxos.ErrChecksumMismatch
}

(lines 1282-1287) can now never execute - version == epaxosRecordCodec is fully intercepted above. That block used to be the terminal check back when epaxosRecordCodec was the top binary-format version; now that v9 is JSON and v8 gets its own explicit finalizeMigration path (1276-1281), this leftover is just confusing dead code sitting between two real migration branches. Delete it.

🧹 Proposed removal
 	if version == 8 {
 		if epaxos.VerifyRecordChecksumWithoutMembershipResult(rec) {
 			return finalizeMigration(rec)
 		}
 		return epaxos.InstanceRecord{}, epaxos.ErrChecksumMismatch
 	}
-	if version == epaxosRecordCodec {
-		if epaxos.VerifyRecordChecksum(rec) {
-			return rec, nil
-		}
-		return epaxos.InstanceRecord{}, epaxos.ErrChecksumMismatch
-	}
 	if version == 7 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/epaxos_storage.go` around lines 1084 - 1096, Remove the obsolete
terminal `if version == epaxosRecordCodec` checksum-validation block in the
legacy decode flow, including its `epaxos.VerifyRecordChecksum` check and
returns. Keep the preceding v8 `finalizeMigration` path and subsequent migration
branches unchanged.

413-423: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

JSON as a content digest is fragile - nil vs. empty slices hash differently.

digestEPaxosCommand hashes json.Marshal(command) to detect "command ID reused with a different command" in applyReadyCommand. Command/Footprint are full of []byte/[][]byte fields; Go's encoding/json encodes a nil slice as null and a non-nil empty slice as ""/[]. Two callers that construct a logically identical command but differ in nil-ness of Payload/Footprint.Points/CycleKey would get different digests, and a legitimate retry could then be rejected as "command ID reused with a different command." This is probably masked today because Propose/decode paths consistently go through append([]byte(nil), ...)/CanonicalizeFootprint, but that's an invariant this digest function doesn't enforce or even document - a future field addition or an alternate construction path (e.g. building Command directly for a test or a different transport) can silently break dedup correctness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/epaxos_storage.go` around lines 413 - 423, Update
digestEPaxosCommand to hash a canonical representation of the command rather
than raw json.Marshal output, normalizing nil and empty []byte/[][]byte fields
such as Payload, Footprint.Points, and CycleKey to the same form. Reuse the
existing command/footprint canonicalization path where applicable, while
preserving deterministic hashing for all command fields.

132-162: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Bound these scans. CreateApplicationCheckpoint and installApplicationSnapshot both use db.pebble.NewIter(nil) and then throw away most keys in memory. applicationStateKey only matches the recordPrefix ('d') range plus the epaxosStorePrefix application-state entries ('e'/'a' and 'e'/'d'), so use bounded iterators for those key ranges instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/epaxos_storage.go` around lines 132 - 162, Update
CreateApplicationCheckpoint and installApplicationSnapshot to replace unbounded
db.pebble.NewIter(nil) scans with bounded iterators covering only the
application-state key ranges: recordPrefix ('d') and the epaxosStorePrefix
entries ('e'/'a' and 'e'/'d'). Iterate each bounded range while preserving the
existing applicationStateKey filtering and snapshot behavior.
epaxos/bootstrap.go (1)

2901-2905: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Same "validate, then decode again" waste in two places. Both applyMembershipControl and CheckpointOffer throw away a validator's already-parsed/canonically-verified wire value and immediately re-run the identical JSON unmarshal + re-marshal + byte-compare on the same bytes.

  • epaxos/bootstrap.go#L2901-L2905: have membershipCommandValidForRef take the already-decoded membershipCommandWire (decoded once by applyMembershipControl) instead of re-decoding control itself.
  • epaxos/checkpoint.go#L630-L644: have validateCheckpointControlMessage return the decoded checkpointControlWire so CheckpointOffer doesn't call decodeCheckpointControl a second time.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/bootstrap.go` around lines 2901 - 2905, Eliminate duplicate
control-message decoding in both validation paths: update epaxos/bootstrap.go
lines 2901-2905 so membershipCommandValidForRef accepts the
membershipCommandWire already decoded by applyMembershipControl, and update
epaxos/checkpoint.go lines 630-644 so validateCheckpointControlMessage returns
its decoded checkpointControlWire for CheckpointOffer to reuse instead of
calling decodeCheckpointControl again.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@epaxos/clone_into_test.go`:
- Around line 9-17: The clone ownership test fixtures must cover all newly owned
mutable buffers, not only Footprint.Points. Update both largeCommand and
smallCommand to initialize Footprint.Spans and CycleKey, then mutate the cloned
span endpoints and cycle key in the relevant assertions to verify
Command.CloneInto does not alias them while preserving the existing points
checks.

In `@epaxos/message.go`:
- Around line 307-309: Keep the early return in Validate for MsgCheckpointVote,
MsgCheckpointCertificate, and MsgCheckpointOffer, and remove those checkpoint
types from every later switch case list, including the duplicate validation
block near the end. Preserve all non-checkpoint cases and their existing
validation behavior.

In `@epaxos/pool.go`:
- Around line 75-84: Update the span-pool cleanup logic to preserve reusable
Start and End endpoint buffers instead of resetting every pooled Span to Span{}.
Apply the existing per-endpoint and aggregate arena limits when retaining
buffers, while still discarding oversized pools; then add a span-shaped
AllocsPerRun test covering ordered-range commands and verifying the
zero-allocation reuse path.

In `@examples/kv/epaxos_storage.go`:
- Around line 557-630: Update ApplyReady and applyReadyCommand so all
Ready.Apply commands share one indexed batch (or equivalent read-your-writes
view), allowing later ordered reads to observe earlier staged writes. Move the
single pebble.Sync commit to the end of ApplyReady, remove per-command batch
creation/commit from applyReadyCommand, and preserve nextTime updates and error
propagation.

In `@examples/kv/kv.go`:
- Around line 467-468: Enforce the configured maximum scan limit in both
CommandForScan and executeOrderedRead, not only during constructor validation.
Reject any positive limit above that maximum before replicas accumulate or
JSON-encode rows, including commands reconstructed from decoded input. Preserve
the existing malformed-scan validation for other invalid values.

In `@tla/EPaxosCertifiedCompaction.tla`:
- Around line 77-88: Add the ~crashed guard to ChooseCheckpoint,
PersistSnapshot, Compact, and Receive so none of these local actions can execute
for a crashed node. Apply the changes at tla/EPaxosCertifiedCompaction.tla
ranges 77-88, 89-97, 115-124, and 153-164 respectively; all four sites require
direct changes.

---

Outside diff comments:
In `@epaxos/sim_test.go`:
- Around line 163-176: The test transports currently normalize protocol messages
and hide invalid production wire output. In epaxos/sim_test.go lines 163-176,
update simCluster.deliver to pass the emitted Message directly to RawNode.Step;
in epaxos/sim_test.go lines 331-340, step the Ready.Messages duplicate directly
and create a separate fixture for target corruption; in epaxos/stress_test.go
lines 109-116, pass the emitted stress message directly to Step. Remove
canonicalTestMessage from these transport paths while preserving explicit
corruption fixtures.

In `@examples/kv/cluster.go`:
- Around line 387-454: The equality helpers sameEPaxosRecordTuple and
sameChosenEPaxosRecord must also compare each record’s Kind, ConfChange, and
ProtocolControl fields. Add these comparisons using the same value semantics as
the core record comparison, while preserving all existing comparisons and helper
behavior.

In `@examples/kv/cmd/kvnode/main.go`:
- Around line 1664-1715: Update the ordered-read branch in handleKV’s GET case
so errors returned by proposeAndWait are sent as 503 Service Unavailable,
matching the PUT, DELETE, transaction, and scan paths. Keep decode,
CommandResult, and other non-proposal failures on the existing 500 response
path.

In `@tests/faultcampaign/invariants.go`:
- Around line 216-225: Update commandsConflict to evaluate all Footprint
representations, not only Points: return true when either command has
Footprint.All, retain point equality checks, and add span-overlap detection
using the same interval semantics as the existing footprint comparison logic.
Reuse sameChosenTuple or its established span-comparison behavior where
appropriate so conflicts match the three-field Footprint contract.

---

Other comments:
In `@EPAXOS.MD`:
- Line 128: Update the ZeroCopyProposals documentation to state that only
footprint buffers are already canonical; describe payload buffers as transferred
or aliased input bytes without calling them canonical. Ensure the wording
remains consistent with the canonical-form guidance near Line 34 and does not
imply embedders must transform payload data before ownership transfer.

In `@examples/kv/cmd/kvnode/main_test.go`:
- Around line 120-123: Update the InstanceRecord fixture in the relevant test
setup to explicitly assign the legacy record kind, ensuring startup reaches the
intended ambiguous legacy timing-domain validation instead of failing on an
unknown zero-value kind.

---

Nitpick comments:
In `@epaxos/bootstrap.go`:
- Around line 2901-2905: Eliminate duplicate control-message decoding in both
validation paths: update epaxos/bootstrap.go lines 2901-2905 so
membershipCommandValidForRef accepts the membershipCommandWire already decoded
by applyMembershipControl, and update epaxos/checkpoint.go lines 630-644 so
validateCheckpointControlMessage returns its decoded checkpointControlWire for
CheckpointOffer to reuse instead of calling decodeCheckpointControl again.

In `@epaxos/conflict_engine_test.go`:
- Around line 191-193: Update the four oracle
helpers—assertConflictEngineMatchesModel’s wantGlobal/wantKeyLanes logic,
modelKeyMax, and assertExactKeyPostings—to use recordHasGlobalConflictScope(rec)
instead of the Kind-only entryHasGlobalConflictScope(rec.Kind) predicate. Extend
randomFoldRecord and randomConflictRecord with generated EntryCommand records
whose Command.Footprint.All is true, ensuring the group-wide scope path is
exercised by the property fuzzer.

In `@epaxos/footprint.go`:
- Around line 212-243: Update footprintsConflict to exploit the sorted,
deduplicated Points and Spans by replacing the nested comparisons with linear
two-pointer merge sweeps. Preserve the existing All handling and half-open
interval semantics, covering point-point, point-span, and span-span conflicts
while reducing each comparison phase to O(n+m).

In `@epaxos/message.go`:
- Around line 536-537: Rename the local boolean declared in the message
emptiness check to avoid shadowing the package-level commandEmpty function, and
update all later references to that boolean within the same function. Keep the
commandEmpty(m.Command) function call unchanged.

In `@examples/kv/epaxos_storage.go`:
- Around line 512-521: Update DB.ApplyReady’s snapshot validation flow to avoid
calling validateApplicationSnapshot twice for SnapshotInstall: skip the
pre-check for epaxos.SnapshotInstall because installApplicationSnapshot
re-validates the checkpoint, while retaining validation for other supported
snapshot modes.
- Around line 1084-1096: Remove the obsolete terminal `if version ==
epaxosRecordCodec` checksum-validation block in the legacy decode flow,
including its `epaxos.VerifyRecordChecksum` check and returns. Keep the
preceding v8 `finalizeMigration` path and subsequent migration branches
unchanged.
- Around line 413-423: Update digestEPaxosCommand to hash a canonical
representation of the command rather than raw json.Marshal output, normalizing
nil and empty []byte/[][]byte fields such as Payload, Footprint.Points, and
CycleKey to the same form. Reuse the existing command/footprint canonicalization
path where applicable, while preserving deterministic hashing for all command
fields.
- Around line 132-162: Update CreateApplicationCheckpoint and
installApplicationSnapshot to replace unbounded db.pebble.NewIter(nil) scans
with bounded iterators covering only the application-state key ranges:
recordPrefix ('d') and the epaxosStorePrefix entries ('e'/'a' and 'e'/'d').
Iterate each bounded range while preserving the existing applicationStateKey
filtering and snapshot behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro

Run ID: f29255b0-9371-4502-974d-a4a51793c9b6

📥 Commits

Reviewing files that changed from the base of the PR and between 82fce6f and d2aff52.

📒 Files selected for processing (99)
  • EPAXOS.MD
  • EPAXOS_IMPLEMENTATION_PROOF.md
  • MODEL_EQ_REPORT.MD
  • README.md
  • RELEASE_SCOPE.md
  • epaxos/bootstrap.go
  • epaxos/bootstrap_core_test.go
  • epaxos/bootstrap_fence_test.go
  • epaxos/bootstrap_recovery_test.go
  • epaxos/branch_test.go
  • epaxos/checkpoint.go
  • epaxos/checkpoint_node.go
  • epaxos/checkpoint_test.go
  • epaxos/checksum.go
  • epaxos/checksum_migration_test.go
  • epaxos/clone_into_test.go
  • epaxos/codec.go
  • epaxos/codec_evidence_scratch_test.go
  • epaxos/config_change_ordering_test.go
  • epaxos/config_crash_migration_test.go
  • epaxos/conflict_engine.go
  • epaxos/conflict_engine_test.go
  • epaxos/conflict_ordering_test.go
  • epaxos/coverage_boundaries_test.go
  • epaxos/doc.go
  • epaxos/dst_test.go
  • epaxos/exhaustion_test.go
  • epaxos/faultsim_harness_test.go
  • epaxos/faultsim_oracle_test.go
  • epaxos/footprint.go
  • epaxos/hard_state_ready_test.go
  • epaxos/internal_test.go
  • epaxos/interval_index.go
  • epaxos/malformed_fuzz_test.go
  • epaxos/message.go
  • epaxos/message_recovery_evidence_test.go
  • epaxos/node.go
  • epaxos/optimized_test.go
  • epaxos/performance_benchmark_test.go
  • epaxos/pool.go
  • epaxos/pool_capacity_test.go
  • epaxos/pool_ownership_test.go
  • epaxos/protocol_coverage_test.go
  • epaxos/ready_durability_test.go
  • epaxos/ready_ownership_additional_test.go
  • epaxos/record_load_test.go
  • epaxos/recovery_boundary_test.go
  • epaxos/recovery_test.go
  • epaxos/remaining_test.go
  • epaxos/retire_test.go
  • epaxos/revisited_test.go
  • epaxos/runtime_stats_test.go
  • epaxos/sim_test.go
  • epaxos/sparse_progress.go
  • epaxos/storage.go
  • epaxos/stress_test.go
  • epaxos/toq_test.go
  • epaxos/types.go
  • epaxos/visit_conflicts_test.go
  • examples/kv/backup.go
  • examples/kv/checkpoint_hard_state_test.go
  • examples/kv/cluster.go
  • examples/kv/cmd/kvcheckpoint/main.go
  • examples/kv/cmd/kvcheckpoint/main_test.go
  • examples/kv/cmd/kvnode/capped_ready_test.go
  • examples/kv/cmd/kvnode/main.go
  • examples/kv/cmd/kvnode/main_test.go
  • examples/kv/cmd/kvnode/performance_benchmark_test.go
  • examples/kv/cmd/kvnode/protocol_driver_test.go
  • examples/kv/cmd/kvnode/retention_test.go
  • examples/kv/epaxos_hard_state_test.go
  • examples/kv/epaxos_record_timing_codec_test.go
  • examples/kv/epaxos_storage.go
  • examples/kv/epaxos_storage_benchmark_test.go
  • examples/kv/footprint_checkpoint_test.go
  • examples/kv/kv.go
  • examples/kv/kv_test.go
  • examples/kv/more_test.go
  • examples/kv/storage_stats_test.go
  • release/EPAXOS_READINESS_EVIDENCE.md
  • tests/faultcampaign/campaign.go
  • tests/faultcampaign/invariants.go
  • tests/faultcampaign/invariants_test.go
  • tests/faultcampaign/main_test.go
  • tests/faultcampaign/native_darwin.go
  • tests/faultcampaign/proxy_test.go
  • tests/faultcampaign/trace.go
  • tests/faultcampaign/trace_test.go
  • tests/lifecyclecollector/collect.go
  • tests/lifecyclecollector/model.go
  • tests/refinementtrace/inventory_test.go
  • tests/refinementtrace/scenarios.go
  • tests/refinementtrace/semantic.go
  • tests/refinementtrace/trace_test.go
  • tests/tla_model_check_runner.py
  • tests/trace_refinement_check.sh
  • tla/EPaxosCertifiedCompaction.tla
  • tla/EPaxosCertifiedCompactionFast.cfg
  • tla/EPaxosCertifiedCompactionFull.cfg
💤 Files with no reviewable changes (1)
  • epaxos/visit_conflicts_test.go
👮 Files not reviewed due to content moderation or server errors (7)
  • epaxos/checkpoint_node.go
  • epaxos/checksum.go
  • epaxos/codec.go
  • epaxos/conflict_ordering_test.go
  • epaxos/doc.go
  • epaxos/internal_test.go
  • epaxos/interval_index.go
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: verification
🧰 Additional context used
🪛 GitHub Check: CodeQL
examples/kv/epaxos_storage.go

[failure] 426-426: Size computation for allocation may overflow
This operation, which is used in an allocation, involves a potentially large value and might overflow.
This operation, which is used in an allocation, involves a potentially large value and might overflow.

🪛 LanguageTool
README.md

[grammar] ~39-~39: Ensure spelling is correct
Context: ...and operational clock discipline remain embedder responsibilities. - The example KV uses...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

MODEL_EQ_REPORT.MD

[grammar] ~38-~38: Ensure spelling is correct
Context: ...shot code, focused lifecycle tests, and TestCheckpointCompactionThreeReplicaRestartSmoke. | | tla/EPaxosRollbackAllocation.tla...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~119-~119: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... - bash tests/tla_model_check_fast.sh passes all 32 configured jobs, including the c...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

EPAXOS.MD

[style] ~101-~101: This phrase is redundant. Consider writing “same”.
Context: ...ponents pause, so all replicas take the same exact application cut. ## Configuration chang...

(SAME_EXACT)

🔍 Remote MCP Github Grep

I found:

  • Public GitHub search hits for the old terms ConflictKeys, CommittedCommand, and Ready.Committed are common in unrelated Go projects, so this PR is a real API/terminology cutover rather than a cosmetic rename. Examples included gogf/gf, dgraph-io/badger, cockroachdb/cockroach, and etcd-io/raft.
  • Searches for the new EPaxos-specific forms (ProvideCheckpoint, EPaxosCertifiedCompaction, ProcessTOQ(now uint64), epaxos.ApplyCommand{}, LoadInstances(epaxos.ExecutionFrontier{}, ...), epaxos.Footprint{Points:) returned no public GitHub matches, so I couldn’t validate these new APIs against external examples.

Comment thread epaxos/clone_into_test.go
Comment on lines 9 to +17
largeCommand := Command{
ID: CommandID{Client: 601, Sequence: 1},
Payload: []byte("large-command-payload"),
ConflictKeys: [][]byte{[]byte("large-key-one"), []byte("large-key-two")},
ID: CommandID{Client: 601, Sequence: 1},
Payload: []byte("large-command-payload"),
Footprint: Footprint{Points: [][]byte{[]byte("large-key-one"), []byte("large-key-two")}},
}
smallCommand := Command{
ID: CommandID{Client: 601, Sequence: 2},
Payload: []byte("small"),
ConflictKeys: [][]byte{[]byte("key")},
ID: CommandID{Client: 601, Sequence: 2},
Payload: []byte("small"),
Footprint: Footprint{Points: [][]byte{[]byte("key")}},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Test every newly owned mutable buffer, not just points.

Add Footprint.Spans and CycleKey to both fixtures, then mutate the cloned span endpoints and cycle key. The current test still passes if Command.CloneInto aliases either field, which would violate the zero-copy/Ready ownership contract.

Also applies to: 44-45, 62-62, 84-84, 92-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/clone_into_test.go` around lines 9 - 17, The clone ownership test
fixtures must cover all newly owned mutable buffers, not only Footprint.Points.
Update both largeCommand and smallCommand to initialize Footprint.Spans and
CycleKey, then mutate the cloned span endpoints and cycle key in the relevant
assertions to verify Command.CloneInto does not alias them while preserving the
existing points checks.

Comment thread epaxos/message.go
Comment on lines +307 to +309
if m.Type == MsgCheckpointVote || m.Type == MsgCheckpointCertificate || m.Type == MsgCheckpointOffer {
return validateCheckpointControlMessage(m)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Checkpoint-type cases added throughout this switch statement below are dead code.

Line 307-309 unconditionally returns for MsgCheckpointVote/MsgCheckpointCertificate/MsgCheckpointOffer before switch m.Type even starts. Every later addition of those three types to a case list is therefore unreachable:

  • Line 323-324 (case MsgPreAcceptResp, ..., MsgCheckpointVote, ...:)
  • Line 400-401
  • Line 439
  • Line 458-459
  • Line 491-492
  • Line 507-508
  • Line 526-527
  • Line 597-598
  • Line 700-701 (case MsgCheckpointVote, ...: return validateCheckpointControlMessage(m) — literally the same call already made at 308, now unreachable)

This isn't just cosmetic clutter in a security-critical validator: it actively misrepresents the control flow to the next person editing this function — they'll reasonably assume checkpoint messages flow through the per-type switches (they don't), and if the line 307-309 guard is ever removed under that assumption, Validate()'s behavior for checkpoint messages silently changes.

Pick one design and delete the other: either drop the early return at 307-309 and let checkpoint types fall through the switches naturally (ending at 700-701), or keep 307-309 and strip the checkpoint cases from every switch below it.

🧹 Minimal fix keeping the early return, stripping the 9 dead branches
-	case MsgPreAcceptResp, MsgAcceptResp, MsgCommit, MsgPrepareResp, MsgTryPreAcceptResp, MsgEvidenceResp,
-		MsgCheckpointVote, MsgCheckpointCertificate, MsgCheckpointOffer:
+	case MsgPreAcceptResp, MsgAcceptResp, MsgCommit, MsgPrepareResp, MsgTryPreAcceptResp, MsgEvidenceResp:

(repeat for lines 400-401, 439, 458-459, 491-492, 507-508, 526-527, 597-598, and delete the now-duplicate case MsgCheckpointVote, MsgCheckpointCertificate, MsgCheckpointOffer: block at 700-701 entirely)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/message.go` around lines 307 - 309, Keep the early return in Validate
for MsgCheckpointVote, MsgCheckpointCertificate, and MsgCheckpointOffer, and
remove those checkpoint types from every later switch case list, including the
duplicate validation block near the end. Preserve all non-checkpoint cases and
their existing validation behavior.

Comment thread epaxos/pool.go
Comment on lines +75 to +84
for i := range spans[:cap(spans)] {
clear(spans[:cap(spans)][i].Start)
clear(spans[:cap(spans)][i].End)
spans[:cap(spans)][i] = Span{}
}
if cap(spans) > maxWireFootprintSpans {
spans = nil
} else {
spans = spans[:0]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Stop throwing away every pooled span endpoint.

Resetting each entry to Span{} guarantees new allocations for every non-empty Start and End, making ordered-range commands miss the pool’s zero-allocation objective. Retain bounded endpoint buffers using per-endpoint and aggregate arena limits, then add a span-shaped AllocsPerRun case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@epaxos/pool.go` around lines 75 - 84, Update the span-pool cleanup logic to
preserve reusable Start and End endpoint buffers instead of resetting every
pooled Span to Span{}. Apply the existing per-endpoint and aggregate arena
limits when retaining buffers, while still discarding oversized pools; then add
a span-shaped AllocsPerRun test covering ordered-range commands and verifying
the zero-allocation reuse path.

Comment on lines +557 to 630
for _, command := range rd.Apply {
if err := db.applyReadyCommand(command); err != nil {
return err
}
}
return nil
}

func (db *DB) applyReadyCommand(command epaxos.ApplyCommand) error {
id := command.Command.ID
if id == (epaxos.CommandID{}) {
return fmt.Errorf("kv: applied command requires a nonzero command ID")
}
digest, err := digestEPaxosCommand(command.Command)
if err != nil {
return err
}
batch := db.newWriteBatch()
defer func() { _ = batch.Close() }()
newRecords, err := stageAndCountEPaxosRecords(batch, rd.Records, db.durableRefs)
value, closer, err := db.pebble.Get(epaxosCommandResultKey(id))
if err == nil {
storedDigest, _, decodeErr := decodeEPaxosCommandResult(value)
closeErr := closer.Close()
if decodeErr != nil {
return decodeErr
}
if closeErr != nil {
return closeErr
}
if storedDigest != digest {
return fmt.Errorf("kv: command ID reused with a different command")
}
return nil
}
if !errors.Is(err, pebble.ErrNotFound) {
return err
}

legacyApplied, err := db.appliedReadyCommand(command.Ref)
if err != nil {
return err
}
var response []byte
batch := db.newWriteBatch()
defer func() { _ = batch.Close() }()
next := db.nextRecordTime()
for _, committed := range rd.Committed {
done, err := db.appliedReadyCommand(committed.Ref)
advancesTime := false
if !legacyApplied {
var handled bool
response, handled, err = db.executeOrderedRead(command.Command)
if err != nil {
return err
}
if done {
continue
}
if _, err := db.stageCommitted(batch, committed, &next); err != nil {
return err
}
if err := batch.Set(epaxosAppliedKey(committed.Ref), []byte{1}, nil); err != nil {
return err
if !handled {
if _, err := db.stageCommitted(batch, command, &next); err != nil {
return err
}
advancesTime = true
}
}
if hardState != nil {
if err := batch.Set(epaxosHardStateKey, hardState, nil); err != nil {
if err := batch.Set(epaxosCommandResultKey(id), encodeEPaxosCommandResult(digest, response), nil); err != nil {
return err
}
if !command.Ref.IsZero() {
if err := batch.Set(epaxosAppliedKey(command.Ref), []byte{1}, nil); err != nil {
return err
}
}
if err := batch.Commit(pebble.Sync); err != nil {
return err
}
addDurableRecords(db.durableRefs, rd.Records)
db.nextTime = next
db.durableRecords.Add(newRecords)
if advancesTime {
db.nextTime = next
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether executeOrderedRead reads through db.pebble (committed-only)
# or through an indexed batch (would allow safely coalescing commits).
rg -n 'func.*executeOrderedRead' -A 25 examples/kv
rg -n 'func.*newWriteBatch' -A 10 examples/kv
rg -n 'NewIndexedBatch' examples/kv

Repository: gosuda/moreconsensus

Length of output: 2014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '330,520p' examples/kv/kv.go
printf '\n----\n'
sed -n '520,700p' examples/kv/epaxos_storage.go
printf '\n----\n'
rg -n 'ApplyReady|ApplyCommitted|executeOrderedRead|OrderedGet|OrderedScan|newWriteBatch|NewIndexedBatch|batch.Get|db.Get\(' examples/kv

Repository: gosuda/moreconsensus

Length of output: 29809


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'OrderedGetResult|OrderedScanResult|ordered get|ordered scan|opGet|opScan|ApplyReady\(\s*epaxos\.Ready' examples/kv/*test.go
printf '\n----\n'
sed -n '2760,2895p' examples/kv/more_test.go
printf '\n----\n'
sed -n '1680,1955p' examples/kv/cmd/kvnode/main.go

Repository: gosuda/moreconsensus

Length of output: 18552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'type Ready struct|Apply \[\]|Records \[\]|MustSync|CommandResult\(|appliedReadyCommand\(' -S .
printf '\n----\n'
sed -n '1,220p' $(git ls-files | rg '(^|/)ready\.go$|(^|/)types\.go$|(^|/)epaxos.*\.go$' | head -n 20)

Repository: gosuda/moreconsensus

Length of output: 23488


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'type Ready struct|Apply \[\]ApplyCommand|MustSync bool|CommandResult\(' .

Repository: gosuda/moreconsensus

Length of output: 2147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '100,140p' EPAXOS.MD
printf '\n----\n'
sed -n '650,710p' epaxos/types.go
printf '\n----\n'
sed -n '980,1045p' examples/kv/epaxos_storage.go

Repository: gosuda/moreconsensus

Length of output: 11723


Batch Ready.Apply before syncing. ApplyReady already treats Ready.Apply as ordered work, so this is burning one WAL fsync per command for no good reason. The current loop opens a fresh batch and calls Commit(pebble.Sync) on every entry; if ordered reads need to see earlier in-Ready writes, use an indexed batch or equivalent read-your-writes view and commit once. examples/kv/epaxos_storage.go:581-626

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/epaxos_storage.go` around lines 557 - 630, Update ApplyReady and
applyReadyCommand so all Ready.Apply commands share one indexed batch (or
equivalent read-your-writes view), allowing later ordered reads to observe
earlier staged writes. Move the single pebble.Sync commit to the end of
ApplyReady, remove per-command batch creation/commit from applyReadyCommand, and
preserve nextTime updates and error propagation.

Comment thread examples/kv/kv.go
Comment on lines +467 to +468
if p.err || len(p.b) != 0 || limit == 0 || limit > uint64(^uint(0)>>1) || reverse > 1 {
return nil, true, fmt.Errorf("kv: malformed ordered scan")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not turn replicated scans into an OOM primitive.

Any positive limit up to MaxInt is accepted, after which every replica accumulates and JSON-encodes that many rows for durable storage. Enforce a configured maximum in both CommandForScan and executeOrderedRead; constructor validation alone is bypassable by decoded commands.

Also applies to: 809-810

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/kv/kv.go` around lines 467 - 468, Enforce the configured maximum
scan limit in both CommandForScan and executeOrderedRead, not only during
constructor validation. Reject any positive limit above that maximum before
replicas accumulate or JSON-encode rows, including commands reconstructed from
decoded input. Preserve the existing malformed-scan validation for other invalid
values.

Comment on lines +77 to +88
ChooseCheckpoint(cut) ==
/\ checkpoint = Zero
/\ cut \in Frontiers
/\ \A lane \in Lanes:
/\ cut[lane] > 0
/\ cut[lane] = executed[lane]
/\ cut[lane] = applied[lane]
/\ checkpoint' = cut
/\ UNCHANGED <<executed, applied, applicationBase, snapshotDurable,
snapshotApp, votes, certified, compacted, records,
summary, crashed, restored, rejected>>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix your zombie crash model.

Your "crash model" lacks basic common sense: a crashed node shouldn't be making checkpoints, persisting snapshots, running compactions, or receiving messages. You remembered to guard Execute, Apply, and ReplayDelta with ~crashed, but completely left it out of the other local actions, allowing the TLC model checker to explore garbage states where a powered-off machine does disk I/O and network processing.

  • tla/EPaxosCertifiedCompaction.tla#L77-L88: Add /\ ~crashed to ChooseCheckpoint so a dead node doesn't initiate a checkpoint.
  • tla/EPaxosCertifiedCompaction.tla#L89-L97: Add /\ ~crashed to PersistSnapshot so a dead node doesn't write snapshots to disk.
  • tla/EPaxosCertifiedCompaction.tla#L115-L124: Add /\ ~crashed to Compact so a dead node doesn't delete records.
  • tla/EPaxosCertifiedCompaction.tla#L153-L164: Add /\ ~crashed to Receive so a dead node doesn't mutate its local storage from incoming packets.
📍 Affects 1 file
  • tla/EPaxosCertifiedCompaction.tla#L77-L88 (this comment)
  • tla/EPaxosCertifiedCompaction.tla#L89-L97
  • tla/EPaxosCertifiedCompaction.tla#L115-L124
  • tla/EPaxosCertifiedCompaction.tla#L153-L164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tla/EPaxosCertifiedCompaction.tla` around lines 77 - 88, Add the ~crashed
guard to ChooseCheckpoint, PersistSnapshot, Compact, and Receive so none of
these local actions can execute for a crashed node. Apply the changes at
tla/EPaxosCertifiedCompaction.tla ranges 77-88, 89-97, 115-124, and 153-164
respectively; all four sites require direct changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants