Skip to content

fix(cgka-engine): phantom committed_from from failed commit staging poisons fork detection#889

Open
dannym-arx wants to merge 2 commits into
masterfrom
fix/phantom-committed-from
Open

fix(cgka-engine): phantom committed_from from failed commit staging poisons fork detection#889
dannym-arx wants to merge 2 commits into
masterfrom
fix/phantom-committed-from

Conversation

@dannym-arx

@dannym-arx dannym-arx commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

marmot

Four send paths (invite, remove-members, capability upgrade, group-data update) recorded "we committed from this epoch" while commit staging could still fail. When staging failed, the cleanup guard cleared the staged OpenMLS commit, but the committed_from entry stayed behind. A marmot that later advanced past that epoch through a peer's commit (settled via convergence, so it holds no fork-recovery snapshot of its own) then mis-routed a legitimate same-epoch sibling commit: the stale entry blocked convergence entry, the WrongEpoch fork branch found no recovery snapshot, and ingest failed closed with ForkedEpoch. The group got stuck in Recovering and rejected further sends.

begin_pending already records committed_from atomically with the pending-publish transition, so the four manual calls were redundant on success and harmful on failure. This PR removes them and deletes the now-unused EpochManager::record_committed_from.

Tests:

  • cgka-engine: failed_invite_staging_does_not_poison_fork_detection reproduces the chain (failed duplicate-member invite, peer-driven advance via convergence, sibling commit for the past epoch). It fails with ForkedEpoch on the pre-fix engine and passes with the fix.
  • cgka-conformance-simulator: failed_invite_staging_does_not_poison_fork_detection_via_harness mirrors the scenario across five marmots on the bus, with a partition keeping the sibling committer at the old epoch. Adds HarnessClient::try_invite so a scenario can drive a deliberately failing invite; registered in SCENARIOS.md.

Verified with just fast-ci, cargo test -p cgka-engine, and cargo test -p cgka-conformance-simulator.


Open in Stage

Summary by CodeRabbit

  • Bug Fixes

    • Fixed failed invite attempts incorrectly affecting later fork detection.
    • Groups now remain usable after a failed invite followed by a concurrent commit.
    • Prevented unnecessary recovery states when processing valid sibling commits.
    • Improved invite error handling so failures are reported instead of causing unexpected crashes.
  • Documentation

    • Added a conformance scenario documenting the failed-invite regression case.

do_send_invite, do_send_remove_members, do_upgrade_group_capabilities,
and do_update_group_data called record_committed_from before (or apart
from) staging the commit. When staging failed after that point, the
cleanup guard cleared the OpenMLS pending commit but nothing pruned the
phantom "we committed from this epoch" entry. Once the client later
advanced past that epoch via a peer's commit settled through
convergence (so no fork-recovery incumbent of its own exists), a
legitimate same-epoch sibling commit was mis-routed: the phantom entry
blocked convergence entry, the WrongEpoch fork branch found no recovery
snapshot, and ingest failed closed with ForkedEpoch, sticking the group
in Recovering.

begin_pending already records committed_from atomically with the state
transition (the Sm1 discipline), so the four manual call sites were
redundant on success and harmful on failure. Remove them and delete the
now-unused EpochManager::record_committed_from (its doc comment claimed
the auto-committer used it; the auto-committer goes through
begin_pending).

Regression test reproduces the full chain: failed duplicate-member
invite, peer-driven advance via convergence, then a sibling commit for
the past epoch must classify instead of failing closed, and the group
must remain usable.
…ted_from

Mirror the cgka-engine regression test
failed_invite_staging_does_not_poison_fork_detection at the multi-client
bus level: Alice fails an invite during commit staging, advances to
epoch 2 by settling Bob's commit through convergence, and a partitioned
Dave later delivers a sibling commit from the same source epoch. The
sibling must be adjudicated deterministically instead of failing closed
with ForkedEpoch, and the group must stay usable for a follow-up
invite.

Adds HarnessClient::try_invite so scenarios can drive a deliberately
failing invite without panicking (invite() now delegates to it), and
registers the scenario in SCENARIOS.md. Verified the scenario fails
with Err(ForkedEpoch) against the pre-fix engine.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d22caeb2-4d5a-47e8-b3af-d90d675a59c5

📥 Commits

Reviewing files that changed from the base of the PR and between 363b1fe and cb279f5.

📒 Files selected for processing (8)
  • crates/cgka-conformance-simulator/SCENARIOS.md
  • crates/cgka-conformance-simulator/src/client.rs
  • crates/cgka-conformance-simulator/tests/canonical_scenarios.rs
  • crates/cgka-engine/src/epoch_manager.rs
  • crates/cgka-engine/src/message_processor/send.rs
  • crates/cgka-engine/src/update_group_data.rs
  • crates/cgka-engine/src/upgrade.rs
  • crates/cgka-engine/tests/fork_detection.rs
💤 Files with no reviewable changes (1)
  • crates/cgka-engine/src/epoch_manager.rs

Walkthrough

Changes

Fork detection staging fix

Layer / File(s) Summary
Defer committed-from bookkeeping
crates/cgka-engine/src/epoch_manager.rs, crates/cgka-engine/src/message_processor/send.rs, crates/cgka-engine/src/update_group_data.rs, crates/cgka-engine/src/upgrade.rs
Removes pre-staging committed-from updates so successful begin_pending performs the bookkeeping atomically.
Expose fallible harness invites
crates/cgka-conformance-simulator/src/client.rs
Adds try_invite error propagation while retaining invite as a panic-on-error wrapper.
Engine regression coverage
crates/cgka-engine/tests/fork_detection.rs
Tests failed staging, sibling commit ingestion, and continued group usability.
Harness regression scenario
crates/cgka-conformance-simulator/tests/canonical_scenarios.rs, crates/cgka-conformance-simulator/SCENARIOS.md
Adds and documents the partitioned sibling-commit scenario through a follow-up invite.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • marmot-protocol/mdk#885 — Addresses stale committed_from state after failed commit-producing sends and its impact on later fork detection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely captures the main fix: phantom committed_from entries from failed staging causing fork-detection issues.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/phantom-committed-from

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

@stage-review

stage-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 4 individual chapters for you:

Title
1 Remove redundant and harmful committed_from recording
2 Add engine regression test for fork detection
3 Expose failing invite helper in simulator harness
4 Add multi-client conformance scenario for phantom commits
Open in Stage

Chapters generated by Stage for commit cb279f5 on Jul 16, 2026 7:02am UTC.

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.

1 participant